Build, extend, or debug apps that consume the Prism iOS/macOS SwiftUI design theme library. Use when applying PrismTheme tokens (color, typography, spacing, elevation, animation, haptic, border, cornerRadius, iconSize, opacity), switching themes (KoreanAesthetic / Neo / Neuromorphic / custom), using Prism components (button style, loading indicators, HUD, alert, popover, sheets), applying Prism transitions (.prism.anvil etc.) or change effects (.prismEffect), or troubleshooting Prism setup with .prismTheme() modifier.
Prism is an SPM-only SwiftUI design-theme library for iOS 17+/macOS 14+. It provides a token-driven theme system, pre-built components, a Pow-backed animations layer, and a rich overlay system.
Pow is an internal dependency — it is never re-exported and its types never appear in Prism's public API.
// Package.swift
.package(url: "https://github.com/your-org/Prism", from: "1.0.0")
// Target dependency
.product(name: "Prism", package: "Prism")
Apply .prismTheme(_:) at the root of the view hierarchy (typically in the App body):
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.prismTheme(.koreanAesthetic)
// Optional: enable Liquid Glass rendering (iOS 26+)
// .prismTheme(.neo, liquidGlass: true)
}
}
}
Access the active theme anywhere with @Environment(\.prismTheme):
@Environment(\.prismTheme) var theme
Every theme exposes 10 token groups through PrismThemeProtocol:
| Group | Protocol / Property | Example access |
|---|---|---|
| Color | PrismColorTokens | theme.colors.primary |
| Typography | PrismTypographyTokens | theme.typography.titleLarge |
| Spacing | PrismSpacingTokens | theme.spacing.md |
| Corner Radius | PrismCornerRadiusTokens | theme.cornerRadius.card |
| Elevation | PrismElevationTokens | theme.elevation.card |
| Border | PrismBorderTokens | theme.borders.subtle |
| Opacity | PrismOpacityTokens | theme.opacity.disabled |
| Icon Size | PrismIconSizeTokens | theme.iconSizes.md |
| Animation | PrismAnimationTokens | theme.animations.standard |
| Haptic | PrismHapticTokens | theme.haptics.selection |
Minimal usage pattern:
struct PriceTag: View {
@Environment(\.prismTheme) var theme
var body: some View {
Text("$9.99")
.font(theme.typography.labelMedium)
.foregroundStyle(theme.colors.primary)
.padding(theme.spacing.sm)
.background(theme.colors.surface)
.clipShape(RoundedRectangle(cornerRadius: theme.cornerRadius.badge))
}
}
| Family | PrismThemeFamily value | Visual character |
|---|---|---|
| Korean Aesthetic | .koreanAesthetic | Soft pastels, rounded forms, delicate typography |
| Neo | .neo | Bold primaries, sharp geometry, high contrast |
| Neuromorphic | .neuromorphic | Soft shadows, extruded surfaces, neutral palette |
Pick variant explicitly:
.prismTheme(.neo) // defaults to .auto (system light/dark)
.prismTheme(.neo.variant(.dark)) // force dark
.prismTheme(.neo.variant(.light))
.prismTheme(.neo.variant(.tinted)) // tinted variant if available
Conform a struct to PrismThemeProtocol and supply all 10 token groups:
struct MinimalTheme: PrismThemeProtocol {
var family: PrismThemeFamily { .custom("Minimal") }
var variant: PrismVariant { .light }
var colors: any PrismColorTokens { MinimalColors() }
var typography: any PrismTypographyTokens { MinimalTypography() }
var spacing: any PrismSpacingTokens { DefaultSpacing() }
var cornerRadius: any PrismCornerRadiusTokens { DefaultCornerRadius() }
var elevation: any PrismElevationTokens { DefaultElevation() }
var borders: any PrismBorderTokens { DefaultBorders() }
var opacity: any PrismOpacityTokens { DefaultOpacity() }
var iconSizes: any PrismIconSizeTokens { DefaultIconSizes() }
var animations: any PrismAnimationTokens { DefaultAnimations() }
var haptics: any PrismHapticTokens { DefaultHaptics() }
var liquidGlass: Bool { false }
}
Apply with .prismTheme(MinimalTheme()).
Button("Submit") { submit() }
.buttonStyle(PrismButtonStyle()) // inherits active theme
.buttonStyle(PrismButtonStyle(theme: myCustomTheme))
PrismSpinner() // ring/dots/pulse; reduce-motion aware
PrismProgressBar(value: $progress, config: PrismProgressBarConfig())
PrismProgressCircle(value: $progress)
PrismSkeletonView { placeholder }
PrismLoadingOverlay(isPresented: $loading) { content }
// View modifier shorthand:
myView.prismLoadingOverlay(isPresented: $loading)
Text("Hello")
.prismText(.titleLarge) // uses active theme typography + color
.prismText(.bodyMedium, color: theme.colors.secondary)
// Show from anywhere:
PrismHUD.show(.success, message: "Saved!")
PrismHUD.show(.error, message: "Failed", duration: 3.0)
PrismHUD.dismiss()
// Register host in root view:
myRootView.prismHUD()
HUD types: .success / .error / .warning / .info / .custom(icon:message:)
myView.prismAlert(
isPresented: $showAlert,
alert: PrismAlert(
title: "Delete?",
message: "This cannot be undone.",
actions: [
PrismAlertAction("Delete", role: .destructive) { delete() },
PrismAlertAction("Cancel", role: .cancel) { }
]
)
)
myView.prismPopover(
isPresented: $showPopover,
content: { Text("Popover body") },
configuration: PrismPopoverConfiguration(placement: .bottom)
)
myView.prismBottomSheet(
isPresented: $showSheet,
detents: [.collapsed, .fraction(0.5), .expanded],
content: { sheetBody }
)
myView.prismCenterSheet(
isPresented: $showDialog,
content: { dialogBody },
config: PrismCenterSheetConfig(role: .dialog)
)
All Prism transitions are accessed via .prism on AnyTransition:
myView.transition(.prism.anvil)
myView.transition(.prism.pop(.orange))
myView.transition(.prism.vanish)
myView.transition(.prism.blinds(slatWidth: 12, style: .venetian))
| Transition | Notes |
|---|---|
.anvil | Insertion only, 1.4 s, haptic |
.blinds / .blinds(slatWidth:style:isStaggered:) | style: PrismBlindsStyle |
.blur | |
.boing / .boing(edge:) | |
.clock / .clock(blurRadius:) | |
.filmExposure | |
.flicker / .flicker(count:) | |
.flip | |
.glare / .glare(angle:color:) | |
.iris(origin:blurRadius:) | |
.move(edge:) / .move(angle:) | |
.pop / .pop(_ style:) | Insertion only, 1.2 s |
.poof | Removal only, 0.4 s |
.skid / .skid(direction:) | direction: PrismSkidDirection |
.swoosh | |
.vanish / .vanish(_ style:) / .vanish(_ style:mask:eoFill:) | Removal only |
.wipe(edge:blurRadius:) / .wipe(angle:blurRadius:) |
Asymmetric example:
row.transition(
.asymmetric(
insertion: .prism.boing(edge: .leading),
removal: .prism.vanish
)
)
Trigger animations on every value change using .prismEffect(_:value:isEnabled:):
likeButton
.prismEffect(.spray { Image(systemName: "heart.fill") }, value: likes)
saveButton
.prismEffect(.shine.delay(0.5), value: hasSaved, isEnabled: hasSaved)
// Particle bursts
.prismEffect(.spray(origin: .center, layer: .local) { star }, value: x)
.prismEffect(.rise { confetti }, value: x)
// Rings
.prismEffect(.pulse(shape: Circle(), count: 2), value: x)
.prismEffect(.pulse(shape: Circle(), style: .tint, count: 1), value: x)
// Motion
.prismEffect(.jump(height: 20), value: x)
.prismEffect(.shake, value: x)
.prismEffect(.shake(rate: .fast), value: x)
.prismEffect(.spin, value: x)
.prismEffect(.spin(axis: (0, 1, 0), rate: .fast), value: x)
// Visual
.prismEffect(.shine, value: x)
.prismEffect(.shine(duration: 2.0), value: x)
.prismEffect(.shine(angle: .degrees(90), duration: 1.5), value: x)
// Haptic (iOS only)
.prismEffect(.feedback(hapticNotification: .success), value: x)
.prismEffect(.feedback(hapticImpact: .medium), value: x)
.prismEffect(.feedbackHapticSelection, value: x)
// Sound (iOS only)
.prismEffect(.feedback(PrismSoundEffect("ding", bundle: .main)), value: x)
// Delay
.prismEffect(.shine.delay(1.0), value: x)
Lift particles above clipping ancestors:
List { rows }
.prismParticleLayer(name: "list")
// In the row:
row.prismEffect(
.spray(layer: .named("list")) { star },
value: count
)
| Type | Values |
|---|---|
PrismParticleLayer | .local; .named(_ name: AnyHashable) |
PrismShakeRate | .default; .fast; .phaseLength(CGFloat) |
PrismSpinRate | .default; .fast; .velocity(initial:maximum:additional:) |
PrismBlindsStyle | .venetian; .vertical |
PrismSkidDirection | .leading; .trailing |
PrismSoundEffect (iOS) | init(_ name: String, bundle:); init(url: URL); .volume(_:) |
Apply theme-aware shadows with .prismElevation(_:):
card.prismElevation(theme.elevation.card)
ElevationLevel carries radius, x, y, opacity, and color parameters.
Use PrismHaptics.trigger(_:) (or triggerHaptic() inside Prism components):
PrismHaptics.trigger(.impact)
PrismHaptics.trigger(.notification)
PrismHaptics.trigger(.selection)
| Mistake | Fix |
|---|---|
Forgetting .prismTheme() at root | Add to App.body or WindowGroup content |
Using Pow types (AnyChangeEffect, ParticleLayer) directly | Use PrismChangeEffect, PrismParticleLayer etc. |
Calling .changeEffect(_:value:) instead of .prismEffect(_:value:) | Use the prism-prefixed modifier |
Using .movingParts. instead of .prism. | Use AnyTransition.prism.* |
Forgetting .prismHUD() on root view | HUD toasts silently fail without a host |
Checking theme.liquidGlass without iOS 26 guard | Gate with if #available(iOS 26, *) |