ThemeKit

mcp
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Pass
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Theme-driven, brand-neutral SwiftUI design system — re-skin your whole app from one accent color. 130+ token-bound components, zero-dependency core.

README.md

ThemeKit

Native, brand-neutral SwiftUI design system — 117 token-bound components that
re-skin from a single accent color: light/dark, per-subtree, zero core dependencies.

CI
Swift
Platforms
Dependencies
License: MIT
npm

Docs · API (DocC) · Wiki · npm (MCP) · Releases · Issues · Changelog

ThemeKit — native SwiftUI design system: 117 components, fully tokenized, per-subtree theming, Swift 6, Liquid Glass, light + dark

The banner above is rendered by ThemeKit itself (its own tokens + components) — the same render pipeline that paints every tile in the gallery.

A theme-driven, brand-neutral SwiftUI component library. Every color,
typography, spacing, radius and shadow is a design token resolved at runtime
from the active Theme, so the whole UI re-skins from a single accent color —
without touching component code. Components never hardcode a color — swap the
theme and everything follows.

import ThemeKit

Features

  • 🎨 Figma → SwiftUI — the MCP's design_to_code (alias figma_to_swiftui) turns a Figma node into
    token-matched, verified-API ThemeKit code with a mapping report (see the
    Advanced — Figma → SwiftUI & MCP section).
  • 🪄 Design Mode — point ThemeKit at a free-form design.md (or a bundled style
    — Linear, Notion, iOS, Brutalist, Pastel) and it re-skins every component to
    match, via an offline heuristic parser (+ an optional LLM path).
  • 🤖 AI-native — a 24-tool MCP server, a Claude Code Agent skill, and an
    llms.txt, so agents generate correct, token-bound UI — all from one source.
  • 🧩 Design tokens everywhere — colors / radius / spacing from JSON, typography /
    shadows in code; one semantic name (fg-hero, rd-sm), different values per theme.
  • 🌈 33 theme presets — ThemeKit's Default plus 32 ready-made color sets
    (cupcake, dracula, cyberpunk, nord…) inspired by daisyUI, each recoloring the
    whole Ant-style palette on device.
  • 📸 Snapshot + render testing — every component renders to a theme-aware PNG via
    ImageRenderer; the suite guards tokens, themes, validation and renders.
  • 117 components — Atoms / Molecules / Organisms, all token-bound.
  • Runtime theming — a Swift token generator + a live configurator turn any
    accent (or base-100) color into a full Ant-style palette on device (no Python,
    no baked files).
  • Validation — pure, testable predicates + a SwiftUI presentation layer.
  • Accessibility — Dynamic Type and Reduce Motion honored throughout.
  • Localization — English-default strings via a bundled String Catalog (with
    Turkish), every default still overridable.
  • Zero-dependency core — Lottie is an opt-in, separate product.
  • DocC catalog, a demo app, and a test suite.

Requirements

Platforms iOS 17+ · macOS 14+
Swift tools 6.2
Dependencies none (core) · lottie-ios 4.4.0+ (only the Lottie add-on)

Installation

Swift Package Manager. In Xcode: File ▸ Add Package Dependencies… and enter
the repository URL, or add it to your Package.swift:

dependencies: [
    .package(url: "https://github.com/isamercan/ThemeKit.git", from: "0.3.0"),
],
targets: [
    .target(
        name: "MyApp",
        dependencies: [
            .product(name: "ThemeKit", package: "ThemeKit"),
            // Optional — only if you need Lottie-backed animations:
            // .product(name: "ThemeKitLottie", package: "ThemeKit"),
        ]
    ),
]

Products

Product Dependencies Use
ThemeKit none the full design system (core)
ThemeKitLottie lottie-ios adds Lottie (After Effects / JSON) animation views; pulls Lottie only if imported

Quick start

Install the theme once at the root, then build with token-bound views:

@main
struct MyApp: App {
    init() { Theme.shared.applyPersistedConfig() }   // restore last-used theme (optional)
    var body: some Scene {
        WindowGroup {
            ContentView().themeKit()            // inject + repaint on theme change
        }
    }
}

struct ContentView: View {
    @ThemeContext private var theme
    var body: some View {
        VStack(spacing: theme.spacing(.md)) {
            Text("Welcome").textStyle(.headingBase)
            PrimaryButton("Get started") { await signIn() }
        }
        .padding(theme.spacing(.base))
        .background(theme.background(.bgElevatorPrimary))
        .cornerRadius(.base)
        .themeShadow(.elevated)
    }
}

Theme.shared.loadTheme(named: "oceanTheme")          // runtime switch

Per-subtree theming

Theming isn't just a global switch — any Theme can be injected 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")   // generated on-device

HStack {
    BookingCard(...)                 // app theme
    BookingCard(...).theme(ocean)    // ocean — this subtree only
    BookingCard(...).theme(grape)    // grape — this subtree only
}

The same components, four injected themes, one screen — brand colors follow the
injected theme while semantic colors (info, success…) stay consistent:

The same components rendered under four injected themes side by side

Every component reads @Environment(\.theme) (default Theme.shared), so this is
additive and backward-compatible. Try it live in the gallery's Theme Injection page.

Theme presets

ThemeKit ships 33 ready-made theme presets — its Default plus 32 color sets
inspired by daisyUI
(cupcake, dracula, cyberpunk, synthwave, nord, coffee…). Each is a
ThemePreset recipe: its accent recolors the whole Ant-style palette and its
base-100 becomes the surface tone, so every theme keeps its signature look —
cupcake stays cream, cyberpunk yellow, dracula slate. The same components,
four injected themes:

The same ThemeKit components rendered under four theme presets — Cupcake, Synthwave, Cyberpunk and Nord

Apply one live, or drop the bundled ThemePicker into any screen for a
theme switcher (it's the demo app's Themes tab):

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

ThemePicker — a grid of all 33 theme presets, each card painted in its own colors

Screenshots

The demo app on device — the component catalog, live theming, the design-token
gallery, and a full booking flow built entirely from ThemeKit components.

Component catalog screen
Component catalog
Live theming screen
Live theming · 33 presets
Design-token gallery screen
Design-token gallery
Theme Generator screen
Theme Generator
Button component demo
Button · variants
DataTable component demo
DataTable · sort/paginate
Example app — hotel search
Example · search
Example app — hotel detail
Example · detail

Real screens from the bundled Demo app, not mockups — every pixel is
a ThemeKit component reading live design tokens.

Components

117 token-bound components, grouped by complexity:

  • Atoms (29) — Badge, Chip, Avatar, Icon, Rating, Spinner,
    StatusDot, Skeleton, ProgressBar, BorderBeam, RollingNumber
  • Molecules (45) — TextInput, OTPInput, Select, Checkbox,
    RadioGroup, Slider, RangeSlider, SearchBar, Tooltip, buttons…
  • Organisms (43) — Card, Carousel, DataTable, Accordion, Steps,
    Timeline, ResultView, Upload, Tour, NavigationBar, ThemePicker

Every component is curated by category in the DocC catalog.

Component gallery

Rendered straight from the library via ImageRenderer (plain <img> so they render everywhere, including the GitHub mobile app) —
regenerate with make screenshots. Interactive overlays (Dialog, Drawer, Tour,
BottomSheet…) and media components are best seen live in the Demo app.

Atoms

Avatar
Avatar
Badge
Badge
Chip
Chip
CountBadge
CountBadge
Divider
Divider
Icon
Icon
Indicator
Indicator
InputLabel
InputLabel
Kbd
Kbd
ProgressBar
ProgressBar
RadialProgress
RadialProgress
Rating
Rating
RollingNumber
RollingNumber
ScoreBadge
ScoreBadge
Skeleton
Skeleton
Spinner
Spinner
StatusDot
StatusDot
Swap
Swap
Tag
Tag
TextLink
TextLink
Title
Title
InlineText
InlineText
BorderBeam
BorderBeam
Join
Join
Mask
Mask
TextRotate
TextRotate
Gauge
Gauge
ShareButton
ShareButton

Molecules

Button
Button
ThemeButton
ThemeButton
Checkbox
Checkbox
CheckboxGroup
CheckboxGroup
RadioButton
RadioButton
RadioGroup
RadioGroup
ToggleGroup
ToggleGroup
ThemeToggle
ThemeToggle
SegmentedControl
SegmentedControl
QuantityStepper
QuantityStepper
Stat
Stat
Steps
Steps
Slider
Slider
Breadcrumbs
Breadcrumbs
TextInput
TextInput
FileInput
FileInput
Pagination
Pagination
Fieldset
Fieldset
DateField
DateField
Select
Select
MultiSelect
MultiSelect
TreeSelect
TreeSelect
Autocomplete
Autocomplete
SearchBar
SearchBar
OTPInput
OTPInput
InputNumber
InputNumber
RangeSlider
RangeSlider
MultiLineTextInput
MultiLineTextInput
Tooltip
Tooltip
Chips
Chips
FilterGroup
FilterGroup
ProgressIndicator
ProgressIndicator
ThemeController
ThemeController
Calendar
Calendar
ColorField
ColorField

Organisms

Accordion
Accordion
AlertToast
AlertToast
Callout
Callout
Card
Card
ChatBubble
ChatBubble
Counter
Counter
Coupon
Coupon
EmptyState
EmptyState
InfoBanner
InfoBanner
KeyValueTable
KeyValueTable
ListRow
ListRow
NotificationCard
NotificationCard
PageHeader
PageHeader
RatingSummary
RatingSummary
ResultView
ResultView
SegmentedTabBar
SegmentedTabBar
Timeline
Timeline
Upload
Upload
PromoBanner
PromoBanner
ListView
ListView
MenuCard
MenuCard
NavigationBar
NavigationBar
FAB
FAB
Hero
Hero
SelectionCards
SelectionCards
CardStack
CardStack
Gallery
Gallery
Footer
Footer
Diff
Diff

Overlays (animated)

Entrance previews rendered from the live components. SelectBox, BottomSheet, Tour and Feedback use OS-owned presentations (native Menu / .sheet) that no offscreen renderer can capture — record them from the running app with make record-gif NAME=SelectBox (boots the simulator, you tap to open the dropdown; see docs/SCREENSHOTS.md).

Dialog
Dialog
Drawer
Drawer
Popconfirm
Popconfirm
AlertToast
AlertToast
Tooltip
Tooltip

Token system

Sources/ThemeKit/
├─ Theme/              # Theme.shared, tokens, generator, configurator API
│  ├─ Theme.swift                 # ObservableObject singleton (Theme.shared)
│  ├─ ColorTokens.generated.swift # Foreground/Background/Border/Text color keys
│  ├─ ThemeModel.swift            # RadiusKey / SpacingKey
│  ├─ Typography.swift            # TextStyle ramp (Montserrat, Dynamic Type)
│  ├─ Shadows.swift               # ShadowStyle + .themeShadow()
│  ├─ SemanticColor.swift         # named palette colors
│  ├─ ThemeGenerator.swift        # runtime palette generator (Swift port)
│  ├─ ThemeConfig.swift           # Codable theme recipe
│  ├─ ThemeKit.swift         # .themeKit() root modifier
│  ├─ ThemeContext.swift          # @ThemeContext property wrapper
│  └─ ThemedHostingController.swift
├─ Components/         # Atoms / Molecules / Organisms (all token-bound)
├─ Validation/         # Validators / ValidationRule / Validator / InfoMessage
├─ Accessibility/      # Reduce Motion + Dynamic Type helpers
├─ Extensions/         # Color(hex:), AspectRatio, Motion, Grid
├─ Utils/              # Haptics, Impression, Localization bridge
├─ Documentation.docc/ # DocC catalog
└─ Resources/
   ├─ *.json                  # defaultTheme / oceanTheme / sunsetTheme (+ Dark)
   ├─ Localizable.xcstrings    # String Catalog (en source + tr)
   └─ Fonts/Montserrat.ttf     # bundled, registered at runtime

Token groups

Group Source of truth Keys
Colors Resources/*.json Theme.ForegroundColorKey · BackgroundColorKey · BorderColorKey · TextColorKey
Radius Resources/*.json Theme.RadiusKey (rd-xsrd-4xl)
Spacing Resources/*.json Theme.SpacingKey (sp-xssp-4xl)
Typography code (Typography.swift) TextStyle — Display / Heading / Label / Body / Overline / Link
Shadows code (Shadows.swift) ShadowStyle — elevated / tabBar / soft

Colors / radius / spacing vary per theme (JSON). Typography & shadows are
structural and constant across themes.

Themes

default (blue) · ocean (turquoise) · sunset (orange) — each with a Dark
variant. Token names are semantic; only the values differ per theme. Add a
theme by dropping a <name>Theme.json into Resources/.

Theming your app (Configurator export)

The library ships a runtime token generator (ThemeGenerator, a Swift port of
tools/gen_tokens.py): from a handful of inputs it regenerates the whole palette,
neutral ramp, surfaces, borders, text, and radius / spacing / font / shadow ramps
— on device, no Python and no baked palette files.

The Demo's Theme Configurator (Colors tab) lets you dial in an accent color +
tint + scale knobs + font + dark and exports a ThemeConfig recipe. To apply one:

// a) one-liner (paste the configurator's "Apply (Swift)" export)
Theme.shared.applyGenerated(primaryHex: "ff0d87", tint: 0.13, radiusScale: 1.0, font: "Montserrat")

// b) ship the Codable recipe as a resource (the configurator's `theme.json`)
let cfg = try ThemeConfig(jsonData: Data(contentsOf: themeJSONURL))
Theme.shared.apply(cfg)
Theme.shared.persistConfig()                            // remember across launches

// c) generator-free: bundle the pre-baked token JSON ("Copy full token JSON")
Theme.shared.setTheme(jsonData: Data(contentsOf: tokensURL))

ThemeConfig is Codable / Sendable / Equatable — persist it, sync it, A/B it.

How live theming works

Components resolve tokens from the Theme.shared singleton (no per-call
environment lookups), so SwiftUI can't infer that an arbitrary view depends on the
theme. .themeKit() closes that gap: it injects Theme into the environment
and (by default) rebuilds the subtree keyed on Theme.revision when the theme
changes, so every view re-reads the regenerated tokens.

  • Switching theme from a settings screen → keep the default
    (reactToRuntimeChanges: true); the whole UI repaints.
  • Editing the theme in-session (a live editor/inspector) → use
    .themeKit(reactToRuntimeChanges: false) so the editor isn't torn down,
    and scope .id(Theme.shared.revision) onto just the live-preview subtree.

Per-subtree theming (\.theme)

Components also read the theme from the \.theme environment value, which
defaults to Theme.shared (so unthemed components never crash). Inject a
different Theme instance to re-theme a branch — a second brand in one screen, or
a pinned theme in a preview/snapshot — without mutating global state:

SomeComponent()
    .theme(brandBTheme)        // this subtree only

This is migrating in: pilot components (Card, Tag) read \.theme today; the
rest still read Theme.shared directly and are moving over incrementally.

Fonts

Montserrat is bundled. System / SystemRounded / SystemSerif / SystemMono
need nothing. Any other family must be registered by the host app (add the .ttf +
UIAppFonts), then pass its PostScript family name as font:.

Accessibility

  • Dynamic Type — the type ramp scales with the user's preferred text size:
    each TextStyle anchors to a semantic Font.TextStyle via relativeTo:. At the
    default size nothing changes; it only grows/shrinks when the user opts in. Clamp
    per-screen if needed: MyScreen().dynamicTypeSize(...DynamicTypeSize.accessibility2).
  • Reduce Motion — decorative/continuous animation is suppressed while
    functional motion is kept: BorderBeam, Skeleton, RollingNumber, StatusDot,
    Carousel autoplay, the OTP caret all calm down; Spinner keeps spinning.

No caller configuration is required — components read the environment directly.

Validation

A pure logic layer (no SwiftUI, no theme) plus a separate presentation layer:

let messages = Validator.validate(email, [.required(), .email()])   // [InfoMessage]
InfoMessageList(messages)                                            // SwiftUI rendering

Feed your own logic — a custom predicate, a regex, a typed Regex, or an async
(server-side) check:

.regex("^[a-z]+$", caseInsensitive: true, "letters only")
ValidationRule("only AAA") { $0 == "AAA" }
let unique = AsyncValidationRule("Username taken") { await api.isAvailable($0) }

FormValidator ties fields, rules, focus and messages together for a whole form.

Localization

User-facing default strings (validation messages, placeholders, accessibility
labels…) come from a bundled String Catalog (Resources/Localizable.xcstrings).
The source language is English; a Turkish translation ships too, and
consumers can add their own.

Every such string is also overridable via API parameters — e.g.
ValidationRule.required("Custom message") — so the catalog only supplies the
default.

Note: a plain swift build copies .xcstrings verbatim (the SwiftPM CLI
doesn't run the catalog compiler), so only English resolves there. Xcode /
xcodebuild compile it, so all bundled localizations resolve in real apps.

⭐ Advanced — Figma → SwiftUI & MCP

ThemeKit is built for the AI-assisted workflow — so generated UI uses the right
component + modifier and resolves colors from tokens, never hardcoded values. One
source (make skill) feeds three surfaces, so they can't drift from the code:

Surface What it does How to use it
MCP server (mcp/) 24 on-demand tools — get_component_api, get_design_tokens, search_components, validate_code, a11y_audit, compose_screen, diff_theme, render_preview, theme_preview, scaffold_screen, design_to_code (alias figma_to_swiftui)… — the agent pulls focused, verified context while it codes. claude mcp add themekit -- npx -y @isamercan/themekit-mcp (or from the repo: cd mcp && npm i && npm run build). Works in any MCP editor — Cursor, Windsurf, Claude Code.
Agent skill (skills/themekit/) A Claude Code skill: idioms + patterns, every component's init & modifiers, the theme presets — generates correct ThemeKit code. /plugin marketplace add isamercan/ThemeKit/plugin install themekit@themekit, or copy skills/themekit/ into .claude/skills/ (zero-install).
llms.txt Structured LLM context about every component, modifier and theme — the llms.txt standard, at the repo root. Point any llms.txt-aware editor (Cursor, Windsurf, Copilot…) at llms.txt.

Then just ask: "Build a sign-up screen. Use the ThemeKit skill." Works with
Claude Code, Cursor, Windsurf, GitHub Copilot, and any tool that supports MCP
or llms.txt.

Figma → SwiftUI

The star tool, design_to_code (alias figma_to_swiftui), turns a Figma node into ThemeKit SwiftUI with
verified APIs instead of guesses: it snaps fills / spacing / radius to design
tokens, maps nodes to components (config-driven via figma-mapping.json, then
heuristics), and returns the code plus a mapping report. Unmapped nodes are
flagged — never silently dropped.

Card {
    VStack(spacing: Theme.SpacingKey.md.value) {
        Badge("Sale").badgeStyle(.error)
        PrimaryButton("Continue") { }
        // ⚠️ unmapped: Mystery Widget (INSTANCE)
    }
}
// 3/4 nodes mapped · fill #f04438 → fg-error (ΔE 0.0) · itemSpacing 16 → sp-md

Set FIGMA_TOKEN in the MCP server's env — it's optional, only this tool needs
it; every other tool works without it. See mcp/README.md for the
full tool list and the figma-mapping.json schema.

Triggering it — paste a Figma link and ask the agent; it pulls fileKey +
nodeId straight from the URL (the URL's node-id=25795-9030 becomes 25795:9030):

Use the themekit MCP. Convert this Figma node to ThemeKit SwiftUI:
https://www.figma.com/design/<FILE_KEY>/App?node-id=<NODE-ID>

Or name the tool explicitly so the agent picks it directly (handy when several MCP
servers are configured):

Connect the themekit MCP and call design_to_code with this url:
https://www.figma.com/design/<FILE_KEY>/App?node-id=<NODE-ID>

Point it at a single screen/frame — it treats a Figma component INSTANCE as an
opaque leaf and won't expand a board of nested instances (see mcp/README.md).

Documentation

📖 Live docs: isamercan.github.io/ThemeKit — the documentation site (guides, component gallery, theming, MCP & DESIGN.md), published from main on every push. The full DocC API reference lives at /api/documentation/themekit.
📚 Wiki — installation, FAQ, troubleshooting, versioning, and contributing guides.

A DocC catalog ships with the package
(Sources/ThemeKit/Documentation.docc). Build it locally in Xcode via
Product ▸ Build Documentation (⌃⌘D), or from the command line:

xcodebuild docbuild -scheme ThemeKit -destination 'generic/platform=iOS'

It curates every component by category and includes guide articles for
Theming, Accessibility, and Validation. No extra dependency required.

Demo

Demo/ — a SwiftUI app (local package reference) with Components (gallery),
Themes (the ThemePicker + a live preview), Colors (token gallery

  • live Theme Configurator), Type, Layout (spacing / radius / shadow
    tokens), and Example (a full flow built from the real components), plus a
    light/dark switcher.

Adding / updating tokens

Colors are generated to keep JSON ↔ Swift in sync:

  1. Update the token maps in tools/gen_tokens.py.
  2. Re-run: python3 tools/gen_tokens.py . (regenerates Resources/*.json +
    ColorTokens.generated.swift).

Radius / spacing live in Resources/*.json (+ the RadiusKey / SpacingKey
enums); typography / shadows in Typography.swift / Shadows.swift.

Testing

swift test

The suite covers the token generator, theme integrity across every bundled theme,
validation, localization, accessibility mapping, and component render smoke tests.

Roadmap

  • Per-subtree \.theme migration — pilot components (Card, Tag) read
    \.theme today; the rest are moving over incrementally so any subtree can be
    re-themed without touching Theme.shared.
  • API stabilization toward 1.0 — the public API is still in 0.x; a minor
    release may include breaking changes until then (see the Versions & Releases wiki).

Shipped: the package is public (MIT), the MCP server is on npm
(@isamercan/themekit-mcp), the Claude Code plugin is installable
(/plugin marketplace add isamercan/ThemeKit), and the DocC docs are live.

Contributing

make ci            # format-lint + lint + build + test (the full gate)
swift test         # the test suite
make screenshots   # re-render component PNGs + rebuild the README gallery
make skill         # regenerate the MCP data, the Agent skill, and llms.txt

Colors are generated — edit tools/gen_tokens.py, then python3 tools/gen_tokens.py .
(see Adding / updating tokens). Keep the build and tests
green; the pre-push hook runs the same gates.

License

MIT © 2026 İsa Mercan. Free for commercial and private use — keep the
copyright notice; the software is provided without warranty.

Acknowledgements

  • Theme presets — the 32 built-in color sets are inspired by daisyUI.
  • Palette ramps — follow an Ant Design-style tonal scale.
  • Montserrat — the bundled type family (SIL Open Font License).
  • Lottie (lottie-ios) — powers the optional ThemeKitLottie add-on.

Reviews (0)

No results found