@ai-created/ui
All components

Theme & media

ThemeProvider / useTheme / ThemeToggle

Application-level dark/light and accent state, persistence, reduced-motion configuration, and a ready-made switch action.

ThemeProvideruseThemeThemeToggleThemeAccentaccentNamesThemeContextValueThemeProviderProps
View source

Component workbench

Configure the production component, inspect it at responsive widths, copy synchronized JSX, and share the exact state from one workspace.

Document theme

The active theme is dark.

Anatomy

Named regions define what belongs to the component, what remains optional, and where semantic responsibility lives.

1

Theme provider

Required

Context owner and document class/data-attribute synchronizer.

2

Motion configuration

Required

Global reduced-motion policy set to user preference.

3

Theme toggle

Optional

Optional 44px icon action that reads and changes context.

button
4

Root theme class

Required

Document hook that activates dark/light semantic token overrides and native color-scheme.

html.light
5

Root accent attribute

Required

Document hook selecting the active accent scheme.

html[data-accent]

Construction and authoring

The Figma-equivalent asset model, auto-layout, resizing, properties, slots, content bounds, localization, and governance contract.

Asset kind

provider

Design library name

Code-only asset

Maturity

stable

Last reviewed

2026-08-29

Canvas applicability

ThemeProvider and useTheme have no canvas geometry. Represent dark and light through Figma variable modes; document ThemeToggle separately as the visible consumer.

Auto layout

Direction
none
Gap
Not applicable
Padding
Not applicable
Alignment
Not applicable
Wrap
Not applicable
  • A context provider and hook do not have a rendered layout box.

Resizing

Width
not-applicable
Height
not-applicable
Minimum width
Not applicable
Maximum width
Not applicable
Minimum height
Not applicable
Maximum height
Not applicable
Overflow
not-applicable
  • Provider and hook dimensions are not applicable; child layout remains unchanged.

Exposed design properties

PropertyTypeDefaultCode mappingOptions or notes
Theme modevariantdarkThemeProvider context and the dark or light Figma variable modedark, light
Accent schemevariantredThemeProvider context and active data-accent selectorred, green, blue, orange, yellow, purple, teal, pink, magenta
Application subtreeRequiredslotNoneThemeProvider childrenNo additional constraint

Nested assets and slots

Theme context

provider / required

Stores and exposes the active mode and accent scheme.

ThemeProvider and useTheme

Motion configuration

provider / required

Honors the user reduced-motion preference.

MotionConfig inside ThemeProvider

ThemeToggle

subcomponent

Ready-made visible mode switch.

ThemeToggle

Content limits

Provider instances

One provider at the application root

Overflow: Remove nested ownership unless an isolated preview explicitly requires it.

Multiple competing global theme owners cause inconsistent persistence.

Localization and RTL

Translatable: ThemeToggle accessible label and title

  • Theme mode is direction-neutral.
  • Toggle placement follows the consuming layout.
  • Test localized mode names in the toggle.
  • Test first render without stored preference.

Responsive behavior

No provider geometry; all descendants retain their own responsive contracts.

Not applicable to provider

  • ThemeToggle preserves its 44px square footprint wherever placed.

Authoring limits

  • There is no Figma component instance for context behavior; use variable modes and a documented visible toggle.
  • Prototype variables do not reproduce localStorage persistence, accent persistence, or pre-hydration theme resolution.
  • Wrap the app once and avoid component-owned global mode changes.
  • Keep destructive and feedback colors independent from the selected accent.
  • Auto layout, padding, gap, resizing, and overflow do not apply to ThemeProvider or useTheme.

Accountable owner

Design systems theming foundations owner

Canonical source: src/components/ThemeProvider.tsx

  • Mode, accent, persistence, root selector, or styles/tokens.css changes require token, rendering, and accessibility review across every scheme and both themes.
  • A breaking property, token, or behavior change requires design and engineering approval, a migration note, and a major release.
  • The design asset, implementation, documentation, accessibility contract, and regression coverage must ship in the same change.
Read the system construction standard

Visual specification

These measurements and semantic tokens define the supported visual contract. Local overrides should preserve the same hierarchy and interaction cues.

ThemeToggle target

44 × 44px

ThemeToggle icon

18 × 18px

Appearance transition

300ms colors / 500ms hero images

Toggle tap scale

0.85×

Toggle hover scale

1.1×

Rules

  • Wrap the application once.
  • Use semantic tokens so components switch without per-component theme or accent branches.
  • Initialize the html class and data-accent before paint in the consuming app to avoid an appearance flash.

Token dependencies

--color-bg / surface / surface2

Theme foundations.

--color-text / text2 / text3

Theme typography hierarchy.

--color-accent* / --color-action-primary* / --color-focus / --color-selection

Role-tuned active accent scheme colors.

--color-success / warning / info / error*

Stable feedback semantics that never follow the accent.

--motion-base / --motion-slow

Theme and image transitions.

States and behavior

A component is not complete until its passive, interactive, disabled, and exceptional states remain understandable in both themes.

dark

Trigger
Default or saved dark preference.
Visual response
Neutral-950 background, light text, dark semantic palette; toggle shows sun.
Behavior
toggleTheme stores light and adds html.light.

light

Trigger
Saved or pre-rendered light preference.
Visual response
Warm-neutral background, dark text, light semantic palette; toggle shows moon.
Behavior
toggleTheme stores dark and removes html.light.

accent scheme

Trigger
Controlled accent prop, or uncontrolled saved accent > existing data-accent > defaultAccent > red.
Visual response
Accent, primary action, focus, selection, and supporting accent roles adopt the selected family.
Behavior
Uncontrolled setAccent persists, updates html[data-accent], and reports through onAccentChange. Controlled accent is fixed; setAccent reports through onAccentChange without persistence, or is a no-op when no callback exists. Destructive and feedback roles remain unchanged.

Accessibility contract

Semantics, accessible naming, keyboard behavior, focus and announcements are implementation requirements, not optional documentation.

Semantics
Context provider plus native motion button; ThemeToggle has a state-dependent aria-label and title.
Accessible name
ThemeToggle announces the action it will perform, such as Switch to light mode.
  • Run a pre-hydration appearance script or equivalent to keep theme, accent, and context state aligned at first paint.
  • Do not encode information solely in the active theme or accent.
  • Every accent role must retain its browser-tested contrast contract across all dark/light foundations.
  • MotionConfig and CSS reduced-motion rules must remain enabled.

Keyboard behavior

Enter / Space
Activates ThemeToggle.
Tab / Shift+Tab
Moves focus to or from ThemeToggle.

Implementation

Use the public package API and preserve the controlled-state, native-attribute, and ref contracts described below.

tsx
import { ThemeProvider, ThemeToggle, useTheme } from '@ai-created/ui';

Client Component required

  • ThemeProvider reads localStorage after mount and falls back to the existing html class or dark.
  • accent is controlled and fixed; defaultAccent is the uncontrolled fallback and red is the default.
  • onAccentChange receives setAccent requests in either mode; controlled changes are never persisted.
  • Uncontrolled precedence is valid localStorage[accent] > existing html[data-accent] > defaultAccent > red.
  • toggleTheme updates context, localStorage, html.light, and temporarily adds theme-transitioning.
  • Uncontrolled setAccent updates context, localStorage[accent], html[data-accent], and calls onAccentChange when provided.
  • The transition class is removed after the 500ms theme-media window, restarted on repeated toggles, and cleaned on unmount.
  • The token stylesheet sets color-scheme for native controls in both modes.
  • useTheme outside a provider receives the safe dark/red default context.
tsx
<ThemeProvider defaultAccent="blue"><ThemeToggle /><App /></ThemeProvider>

Public API

These props and helpers are compatibility contracts. Changes require migration guidance and release notes.

PropTypeDefaultContract
ThemeProvider.childrenReactNoderequiredApplication subtree.
ThemeProvider.accentAccentundefinedControlled, fixed accent; wins over storage and defaults.
ThemeProvider.defaultAccentAccentredUncontrolled fallback after storage and document accent.
ThemeProvider.onAccentChange(accent: Accent) => voidundefinedReceives setAccent requests in either mode; controlled changes are not persisted.
useTheme().themedark | lightdarkCurrent theme.
useTheme().accentAccentredCurrent accent scheme.
useTheme().setAccent(accent: Accent) => voidundefinedPersists uncontrolled changes; controlled mode reports through the callback or is a no-op.
useTheme().toggleTheme() => voidundefinedSwitches and persists the theme.
ThemeTogglecomponentundefinedReady-made theme switch control.

Usage guidance

These rules keep the component recognizable across products without preventing deliberate composition.

Do

  • Apply the initial document class before React hydration.
  • Use useTheme only for behavior or media that cannot be expressed through tokens.

Do not

  • Do not hard-code dark-only colors inside components.
  • Do not nest competing ThemeProviders.

Testing contract

The component should be verified as rendered UI, an interaction model, an accessibility surface, and a theme-aware visual artifact.

Unit

  • Resolves saved, existing-class, default themes, and uncontrolled accent precedence.
  • Controlled accent wins over storage/document/default; callback and no-op behavior are covered.
  • toggleTheme updates context, storage, and document class.
  • Uncontrolled setAccent updates context, storage, and html[data-accent].

Interaction

  • ThemeToggle changes mode by pointer and keyboard and updates its label/icon.

Accessibility

  • Accent choices have an accessible name and do not communicate meaning by color alone; reduced-motion preference is honored.

Visual

  • Capture every foundation and public component across all nine accents and both themes, including transition and no-motion states; browser-test accent text, muted text, borders, actions, focus, selection, and action foregrounds on bg, surface, and surface2.