Theming
A Theme is a full set of design tokens — colors, type, spacing, radius, shadow.
Every ThemeKit component reads tokens from the active theme via
@Environment(\.theme), so re-skinning is a single assignment.
Theme presets
Section titled “Theme presets”ThemeKit ships 33 ready-made presets: its Default plus 32 color sets inspired by daisyUI (cupcake, dracula, cyberpunk, synthwave, nord, coffee…). Each preset’s accent recolors the whole Ant-style palette while keeping its signature surface tone.
ThemePreset.named("dracula")?.apply() // recolors Theme.shared on the fly
@State private var active: String? = "cupcake"ThemePicker(selection: $active) // a tappable grid of all 33 themes
Generate a theme from one color
Section titled “Generate a theme from one color”Don’t have a design system? Generate a full palette on-device from a single accent hex — ThemeKit derives the Ant-style scale for you.
let theme = Theme()theme.applyGenerated(primaryHex: "#7C3AED") // full palette from one accentImport a CSS theme (HeroUI, Tailwind, shadcn…)
Section titled “Import a CSS theme (HeroUI, Tailwind, shadcn…)”Already have a web design system as CSS custom properties? Hand it to ThemeKit
directly — the oklch() / hex variables are parsed on-device at runtime and
the whole token set is generated for you. No JSON, no build step. ThemeKit even
ships a ready-made HeroUI theme.
Drop a .css in your app and apply it in one line:
Theme.shared.loadTheme(cssNamed: "heroui", font: "Inter") // bundled HeroUI themeTheme.shared.loadTheme(cssNamed: "brand") // your own brand.css in the app bundleOr apply a CSS string — from a file, a network response, anywhere:
let css = try String(contentsOf: url) // your theme.cssTheme.shared.setTheme(css: css) // parsed + applied instantly, no restartTheme.shared.setColorScheme(dark: true) // switches to the CSS's .dark blockBoth the :root/.light and .dark blocks are read: --accent drives the
primary/info palette, --danger / --success / --warning the semantic colors,
and --background / --foreground / --border / --muted the neutral surfaces
and text (--radius / --field-radius → the box/field radius roles). Anything the
CSS doesn’t define falls back to ThemeKit’s defaults, and the CSS is treated as
untrusted text — only --var: value; declarations are read, nothing is executed.
Per-subtree theming
Section titled “Per-subtree theming”Theming isn’t only a global switch. Inject any Theme into a single subtree with
.theme(_:), and every component inside re-skins to it — no Theme.shared
mutation, no global state. The rest of the app keeps its theme.
let ocean = Theme(); ocean.loadTheme(named: "oceanTheme")let grape = Theme(); grape.applyGenerated(primaryHex: "#7C3AED")
HStack { BookingCard(...) // app theme BookingCard(...).theme(ocean) // ocean — this subtree only BookingCard(...).theme(grape) // grape — this subtree only}Brand colors follow the injected theme while semantic colors (info, success…)
stay consistent. Because every component defaults to Theme.shared, this is
fully additive and backward-compatible.