Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.prism.byescaleira.com/llms.txt

Use this file to discover all available pages before exploring further.

A theme in PrismUI is a concrete implementation of the PrismTheme protocol that maps every ColorToken to a Color. Themes live in the SwiftUI environment, so every component below the injection point — no matter how deeply nested — automatically reads the right color without you passing anything explicitly. Switching themes at runtime triggers a re-render of the entire tree.

Built-in themes

PrismUI ships four themes that cover the most common needs.

DefaultTheme

Apple HIG system colors with automatic light/dark adaptation. The right choice for most apps.

DarkTheme

Always-dark surfaces regardless of the system appearance setting. Useful for media players and cinematic sections.

HighContrastTheme

Maximum contrast beyond WCAG AAA. Use for accessibility-first products or as an opt-in user preference.

BrandTheme

Configurable primary, secondary, and accent colors. The right choice when you need to match a brand identity.

Applying a theme

Inject a theme into any view hierarchy with .prismTheme(). Place it at the root of your app to apply it everywhere, or on a specific subtree to scope it.
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .prismTheme(DefaultTheme())
        }
    }
}
You can also inject a theme together with a ColorScheme override using .prismEnvironment():
ContentView()
    .prismEnvironment(theme: DarkTheme(), colorScheme: .dark)

BrandTheme

BrandTheme accepts three Color values — primary, secondary, and accent — and maps them to the brand, variant, and interactive token roles respectively. All other tokens (backgrounds, surfaces, borders, feedback colors) remain consistent with the system defaults.
let theme = BrandTheme(
    primary: .indigo,
    secondary: .mint,
    accent: .orange
)

ContentView()
    .prismTheme(theme)
All three parameters have defaults, so you can override only what you need:
// Only change the brand color
BrandTheme(primary: .purple)

// Change brand and accent
BrandTheme(primary: .teal, accent: .pink)

Auto-generated color harmonies

PrismAutoTheme derives a complete BrandTheme from a single brand color using color theory. Four harmony strategies are available:
// Secondary = +30° hue, accent = complementary (+180°)
let theme = PrismAutoTheme.generate(from: .indigo)
Use the Harmony enum when you want to pick a strategy dynamically:
let theme = PrismAutoTheme.generate(from: .blue, harmony: .triadic)
ContentView().prismTheme(theme)

Persisted theme switching with PrismThemeStore

PrismThemeStore persists the user’s theme choice to @AppStorage and restores it on next launch. It supports animated transitions between themes.
1

Create and inject the store

Create a PrismThemeStore at the app level and apply it with .prismThemeStore(). The store starts with DefaultTheme and restores any previously saved preference automatically.
@main
struct MyApp: App {
    @StateObject private var themeStore = PrismThemeStore()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .prismThemeStore(themeStore)
                .environmentObject(themeStore)
        }
    }
}
2

Register custom themes

Register additional themes by identifier before injecting the store, or at runtime.
let store = PrismThemeStore(customThemes: [
    "brand": BrandTheme(primary: .indigo, secondary: .mint, accent: .orange),
])
3

Switch themes from a settings view

Read the store from the environment and call setTheme(_:animated:) to switch:
@EnvironmentObject private var themeStore: PrismThemeStore

Picker("Theme", selection: Binding(
    get: { themeStore.currentIdentifier },
    set: { themeStore.setTheme($0) }
)) {
    ForEach(themeStore.availableThemes, id: \.self) { id in
        Text(id).tag(id)
    }
}

Implementing the PrismTheme protocol

For full control over every color role, implement PrismTheme directly. Your type must be Sendable and annotated with @MainActor.
import PrismUI
import SwiftUI

public struct MidnightTheme: PrismTheme, Sendable {

    public init() {}

    public func color(_ token: ColorToken) -> Color {
        switch token {
        case .brand:               Color(red: 0.4, green: 0.6, blue: 1.0)
        case .brandVariant:        Color(red: 0.4, green: 0.6, blue: 1.0).opacity(0.8)

        case .background:          Color(white: 0.04)
        case .backgroundSecondary: Color(white: 0.08)
        case .backgroundTertiary:  Color(white: 0.12)

        case .surface:             Color(white: 0.08)
        case .surfaceSecondary:    Color(white: 0.12)
        case .surfaceElevated:     Color(white: 0.16)

        case .onBackground:            .white
        case .onBackgroundSecondary:   .white.opacity(0.7)
        case .onBackgroundTertiary:    .white.opacity(0.35)
        case .onSurface:               .white
        case .onSurfaceSecondary:      .white.opacity(0.65)
        case .onBrand:                 .white

        case .border:        .white.opacity(0.14)
        case .borderSubtle:  .white.opacity(0.07)
        case .separator:     .white.opacity(0.10)

        case .interactive:         Color(red: 0.4, green: 0.6, blue: 1.0)
        case .interactiveHover:    Color(red: 0.4, green: 0.6, blue: 1.0).opacity(0.9)
        case .interactivePressed:  Color(red: 0.4, green: 0.6, blue: 1.0).opacity(0.7)
        case .interactiveDisabled: .white.opacity(0.2)

        case .success: Color(red: 0.3, green: 0.85, blue: 0.4)
        case .warning: Color(red: 1.0, green: 0.8, blue: 0.3)
        case .error:   Color(red: 1.0, green: 0.4, blue: 0.35)
        case .info:    Color(red: 0.4, green: 0.7, blue: 1.0)

        case .shadow:  .black.opacity(0.35)
        case .overlay: .black.opacity(0.65)
        }
    }
}
Apply your custom theme the same way as any built-in theme:
ContentView()
    .prismTheme(MidnightTheme())
Your color(_:) method must handle all 28 ColorToken cases. The Swift compiler will warn you if you miss one, because ColorToken is a non-exhaustive enum — add a default fallback only if you intentionally want to inherit a base style.

Reading the active theme in custom components

Access the current theme in any view using the prismTheme environment value:
struct PriceLabel: View {
    @Environment(\.prismTheme) private var theme
    let amount: String

    var body: some View {
        Text(amount)
            .font(TypographyToken.title2.font(weight: .bold))
            .foregroundStyle(theme.color(.onBackground))
            .padding(SpacingToken.sm.rawValue)
            .background(theme.color(.surfaceSecondary), in: RadiusToken.md.shape)
    }
}
The environment value updates automatically when the parent injects a new theme, so your component re-renders without any extra subscription code.