Theming
Four theming axes (light/dark, palette, glass surface, density) on one model: ThemeProvider carries scheme, palette and glass on every platform; on the web, helpers persist all four choices and update the CSS handoff attributes.
Native (ThemeProvider)
On iOS and Android, wrap the app once in ThemeProvider. It follows the OS appearance by default; force a scheme with the dark / light boolean, and the material with glass / solid. Both axes are flat booleans like every Canvas axis: pass at most one per axis, omit both for the default (the OS appearance, the platform material). No CSS and no <html> attributes are involved on native.
tsx
Read the active theme anywhere under the provider with useTheme().
tsx
Density
is a per-component choice, not a provider prop: pass a density boolean to the components that support it (for example <Card compact> or <Card comfortable>). That holds for React Native Web components too; the setDensity helper also updates spacing tokens in the separate CSS handoff.
Server rendering (SSR/SSG)
When the app server-renders (Next.js and the like) and the client scheme can differ from the server default (a stored preference, the OS), also pass ssrScheme with the scheme the server resolves, and ssrPalette likewise for a stored palette. The provider repeats them for the hydration render so the HTML matches, then applies the requested scheme and palette after mount. Without it React keeps the server's inline colors on elements that never re-render, leaving components stuck in the server's scheme. A server cannot measure a window either, so the viewport tier renders base, the desktop variant, there; an app that knows it is serving a narrow client passes ssrBreakpoint with the bucket to assume (sm for a phone), and the real window's bucket applies right after hydration.
Typefaces and nested providers
Hand the faces the app registered to the root provider's fonts prop, once: one family that carries every weight, or a face per weight. They are what the app loaded, not a theme choice, so a nested ThemeProvider that omits fonts keeps the nearest parent's faces, in the overlays it opens too. One that passes fonts uses its own map in place of the parent's (the roles are not merged), and an empty map returns that subtree to the system face.
No other theme axis is inherited. A nested provider resolves its scheme, palette, surface and tokens from its own props: omitting dark / light follows the OS appearance, omitting mint paints blush, and omitting tokens paints its palette unbranded. That is how a subtree shows a palette or brand of its own, as the palette previews on this page do. Pass the same tokens constant again to carry a rebrand into a nested provider, and pass both dark={dark} and light={!dark}, with dark read from useTheme(), to follow the parent's scheme (with dark alone, a light parent leaves the nested provider on the OS appearance).
tsx
Light / Dark Mode
Light mode is the default. On the web, dark mode is the dark class on <html>, which the helpers toggle so the token layer flips every color token for anything styled with the CSS variables. Mirror the class into ThemeProvider's scheme so the components follow (the Integration page shows the hook; the value form exists exactly for a held value like that). (On native, pass the dark / light boolean to ThemeProvider instead.)
tsx
Light
Dark
Clear text fields
In glass mode, web Input, Textarea, Autocomplete, InputOTP, Stepper, PhoneInput and DataTable editors use a clear material: Dark Factory's field well, a light tint and a hairline with no blur, so the page reads through unsoftened. Text, selection and the caret stay sharp. The fields stay still when clicked or typed into. Native fields keep their platform material. Bare and underlined editors retain their existing shape.
Web helpers
tsx
Respecting system preference
On the web the helpers do not auto-detect prefers-color-scheme. Wire it yourself:
tsx
Palettes
The light scheme comes in two Dark Factory palettes: blush, the default, and mint. Pass the mint boolean to ThemeProvider for mint and omit it for blush. Dark Factory ships one dark palette, so dark wins over mint: a dark provider paints the same colors whichever light palette it names. useTheme() reports the requested palette as palette, and mintColors and colorsFor(palette, scheme) expose the token sets.
tsx
Blush
✓
Email updates
Sync
Mint
✓
Email updates
Sync
Both previews render in the light scheme, where the two palettes differ.
Web helpers
The helpers persist the choice and set data-palette on <html>, which the CSS handoff reads; the dark class still wins over it. Feed the same choice to ThemeProvider so React Native components follow it.
tsx
Glass Surface
Glass is a theme preference, not a prop to add to every component. The material contract distinguishes stable content glass from functional Liquid Glass. Reading surfaces and text-entry wells stay still; floating navigation, controls and overlays can use liquid material where their role calls for it. Labels, images, chart marks and layout wrappers do not become separate glass panels. Tint density and custom motion are separate decisions, and native Liquid Glass does not need added wobble.
Omit both surface booleans to follow the platform default: glass on supported iOS 26+ and solid elsewhere. Use glass or solid on ThemeProvider to choose an appearance. Solid means opaque fill, readable foreground, boundaries and elevation. Accessibility preferences and available native or browser material determine the rendered result. Switching appearance must preserve focus, values, open controls and scrolling. Components read this preference from the provider, not from CSS.
tsx
Solid
Glass
What changes
- The kit's shared material rendering owns material selection and clipping. Native Liquid Glass, native frost and the browser's frost are different capabilities; a browser preview of an iOS skin does not prove native rendering
- No semantic token changes: popover and card keep the same opaque values they carry in solid mode. Glass adds its own fills, glass-tint, glass-tint-content, glass-tint-control and glass-tint-dense, painted under the material by the surfaces of each layer
- Brand and status meanings remain readable in every material. Check contrast against actual backgrounds, including scrolling content; a tint token or a decorative rim alone does not establish it
- Solid surfaces retain their full opaque treatment without glass capture. Reduce Transparency and Increase Contrast require readable opaque treatment; Reduce Motion removes nonessential movement without requiring opacity by itself
- Android blur needs a safe live backdrop target. The optional @nannier/canvas-blur integration on Android 12+ with Expo SDK 57 enables capture only while glass needs it. OverlayProvider supplies safe overlay targets. Missing or unsafe material uses the complete solid skin
- The CSS handoff always carries the web material's tints, blur and hairline; data-surface marks glass mode and gates the accessibility and print fallbacks that turn the material opaque. It paints no page backdrop of its own and supplies no native material
Web helpers
The helpers persist the choice and update the CSS handoff attributes. Feed the same choice to ThemeProvider so React Native components follow it.
tsx
Density
Density controls spacing in content areas and data tables, and it is a per-component choice on every platform: pass the compact or comfortable boolean to the components that support it (for example Card and DataTable). The separate CSS handoff supports an app-wide density attribute for the card and table spacing tokens it defines.
tsx
Web helpers
The helpers persist an app-wide preference (compact, regular, comfy) and set data-density on <html>, which the CSS handoff reads. Read the stored value back and map it to component booleans to apply that preference to React Native components.
tsx
Combining Axes
All four axes are independent and composable. Scheme, palette and surface are ThemeProvider props on every platform; density is per component. The web helpers persist all four choices and update the CSS handoff through the dark class, data-palette, data-surface, and data-density. Keep provider state and component booleans in sync with those choices.
tsx
tsx