Themekit
Use when writing SwiftUI UI with the ThemeKit design system — its tokenized components (Badge, Card, TextInput, Select, Carousel…), runtime theming, theme presets, and chainable modifiers. Trigger on "ThemeKit", design tokens, theme switching, or a request to build a screen with ThemeKit.From its SKILL.md
npx -y skills add isamercan/ThemeKit --skill themekitAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
5.9 KB, as published. Nobody here has run it
ThemeKit
A token-driven, brand-neutral SwiftUI component library. Every color,
radius, spacing, type style and shadow is a design token resolved at runtime
from the active Theme. Write UI with its components + modifiers; the theme
re-skins everything.
For the full component list + each one's init params + modifiers, read
references/components.md. For the theme-preset
catalog, read references/themes.md.
Golden rules
- Never hardcode a color. No
.foregroundStyle(.blue), noColor(hex:)in app code. Pull from the theme:theme.text(.textPrimary),theme.background(.bgWhite), or aSemanticColor(SemanticColor.primary.base). - Read the theme from the environment:
@Environment(\.theme) private var theme. It defaults toTheme.shared. - Inject it once at the root:
.environment(Theme.shared). - Set styling with chainable modifiers, not extra init args. Required
content/bindings/actions go in
init; variants, sizes, flags, colors and callbacks are modifiers — e.g.Badge("New").badgeStyle(.info).badgeShape(.rounded). - Sizes use the native modifier:
.controlSize(.small)(not asize:arg). Disabled state is native:.disabled(_:)(not anisEnabled:arg).
Setup
import ThemeKit
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView().environment(Theme.shared) // inject the theme once
}
}
}
struct ContentView: View {
@Environment(\.theme) private var theme
var body: some View {
VStack(spacing: Theme.SpacingKey.md.value) {
Text("Hello").foregroundStyle(theme.text(.textPrimary))
PrimaryButton("Continue") { /* … */ }.fullWidth()
}
.padding(Theme.SpacingKey.lg.value)
.background(theme.background(.bgWhite))
}
}
Tokens
- Text:
theme.text(.textPrimary | .textSecondary | .textTertiary | .textDisabled | .textHero) - Surfaces:
theme.background(.bgWhite | .bgElevatorPrimary | .bgSecondary | .bgHero | …)— default component surface is.bgWhite(base-100) - Borders:
theme.border(.borderPrimary | .borderHero | …) - Foreground:
theme.foreground(.fgHero | .fgSecondary | …) - Semantic colors (
SemanticColor):.primary .secondary .accent .neutral .info .success .warning .error+ hues. Each gives.solid .soft .accent .bordervariants and a 50..900 ladder (.base .hover .active .strong .bg …). Use withFillVariant(.solid .soft .outline .ghost) on the configurable components. - Radius — by role (re-rounds a whole category from one token):
Theme.RadiusRole.box.value(cards/modals),.field.value(buttons/inputs),.selector.value(checkboxes/badges). Size ramp also exists:Theme.RadiusKey.sm.valueetc. - Spacing:
Theme.SpacingKey.xs|sm|md|base|lg|xl.value - Type:
.textStyle(.headingSm | .bodyBase400 | .labelMd600 | …)(a view modifier).
Components
Browse references/components.md for all of them.
The configurable ones take a SemanticColor + FillVariant, so brand/accent
recolor for free:
Badge("Sale").badgeStyle(.error).variant(.solid)
Chip("Pool", isSelected: $on).icon("drop.fill")
ThemeButton("Save") { save() }.color(.accent).variant(.solid)
TextInput("Email", text: $email).icon(leading: "envelope")
.a11yID("login.email")
Select("Country", options: countries, selection: $country) { $0.name }
Checkbox("I agree", isChecked: $agree).controlSize(.small)
Card(title: "Booking") { /* content */ }
Rating(value: 4.5).allowHalf().onRate { rating = $0 }
ProgressBar(value: 0.6).showsPercentage().gradient()
Theming
// Recolor the whole app from one accent (generates the full palette on-device):
Theme.shared.applyGenerated(primaryHex: "7C3AED")
// Or a full config (accent + base surface + secondary/accent brand colors):
Theme.shared.apply(ThemeConfig(primaryHex: "ff79c6", baseHex: "282a36",
secondaryHex: "bd93f9", accentHex: "ffb86c", dark: true))
// Inject a theme into ONE subtree only:
let ocean = Theme(); ocean.applyGenerated(primaryHex: "0fb4ab")
BookingCard().theme(ocean)
Theme presets
ThemeKit bundles 33 theme presets (inspired by daisyUI). Apply one, or drop the ThemePicker
grid into a screen:
ThemePreset.named("dracula")?.apply() // recolor Theme.shared live
@State private var active: String? = "cupcake"
ThemePicker(selection: $active) // tappable grid of all 33 themes
Patterns
Themed form
@Environment(\.theme) private var theme
Card(title: "Sign up") {
VStack(spacing: Theme.SpacingKey.md.value) {
TextInput("Email", text: $email).icon(leading: "envelope").a11yID("email")
TextInput("Password", text: $pw, isSecure: true).a11yID("pw")
Checkbox("Accept terms", isChecked: $terms)
PrimaryButton("Create account") { submit() }.fullWidth().disabled(!terms)
}
}
Theme switcher screen — see ThemePicker + the theme-presets section above.
Anti-patterns (don't)
- ❌
.foregroundStyle(.blue)/Color(hex:)in app UI → ✅ a theme token. - ❌
Badge("x", size: .small)→ ✅Badge("x").size(.small)(size/variant/style are modifiers now — checkreferences/components.mdfor each component's set). - ❌
SomeControl(isEnabled: false)→ ✅SomeControl().disabled(true). - ❌ Re-implementing a Card/Sheet/Toast → ✅ use the existing component.
- ❌ Hardcoded corner radius
cornerRadius: 12→ ✅Theme.RadiusRole.box.value.