Theming
Tokens, the light and dark palettes, and theming a navigator from them.
theme.css is the one file that is yours. Everything else init puts in styles/ is plumbing —
the engines, the size scale, the barrel — and none of it needs touching to retheme an app. Replace
theme.css whole and every component repaints.
Three ways to fill it:
- Build one. Theme composes seven axes and shows the file. Copy the
theme.csstab over yours. Nothing to run. - Bring a web app's theme. Paste a shadcn or tweakcn
globals.cssovertheme.cssand rundelacour theme, which rewrites the file in place. - Edit it. The palette is shadcn's, name for name, so the names are the ones you already know.
The palette is shadcn's, in shadcn's shape. A theme built for a web app — by hand, by
shadcn init, or in a generator like tweakcn — is the same set of variable names this library
paints from, so moving one across is a copy, not a translation.
Two layers, in theme.css. The raw semantic names are declared once per theme under
@variant light / @variant dark, and an @theme inline block maps each onto the --color-*
Tailwind mints utilities from:
@layer theme {
:root {
@variant light { --primary: oklch(0.205 0 0); }
@variant dark { --primary: oklch(0.922 0 0); }
}
}
@theme inline {
--color-primary: var(--primary);
}Components reference the semantic name — bg-primary, text-muted-foreground — and never a raw
palette colour or a dark: prefix. The variable swap handles the theme.
Bring your web theme
Paste the file over theme.css, then:
bunx delacour@latest themeOr point the command at the source and it writes theme.css for you:
bunx delacour@latest theme ./src/app/globals.cssbunx delacour@latest theme https://tweakcn.com/r/themes/<id>It also reads stdin, which is the shortest path from a generator's Copy button:
pbpaste | bunx delacour@latest theme -The one thing that cannot survive a straight paste is the wrapper. Uniwind reads a theme only from
@variant light / @variant dark; a literal .dark { … } is registered as a utility class named
dark and contributes nothing — no error, no warning, just a dark theme that never arrives. The
command rewrites those two lines, fills in the tokens shadcn has no name for, and reports anything
React Native cannot use. delacour doctor fails on a theme.css still in shadcn's shape, so a
forgotten run is caught before a build.
Font stacks are the one value that does not carry. React Native's fontFamily takes a single
family name with no fallback list, so "Inter, ui-sans-serif, system-ui" resolves to nothing and
falls back to the system sans. delacour theme keeps the first family and tells you what it
dropped.
The palette
Every token shadcn defines, under the name shadcn gives it — plus elevated, tertiary,
success, warning, info and overlay, which it has no name for and which every component here
paints with. Those six are derived from tokens a shadcn theme does define, so a palette you
bring across drags them along instead of leaving them on this package's defaults.
Tokens is the full reference: what each one means, what it resolves to in both themes, and how each derivation is built.
Overriding a token
Redeclare it in your own CSS, after the library's import. Raw names, not --color-*.
@import "@delacour/react-native-ui/styles";
@layer theme {
:root {
@variant light {
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.985 0 0);
}
@variant dark {
--primary: oklch(0.707 0.165 254.624);
--primary-foreground: oklch(0.205 0 0);
}
}
}Declare a token in both variants. Uniwind refuses to build a theme that declares a variable the other one does not — "All themes must have the same variables".
Corners
Every generic rounded-* step is derived from a single --radius, so setting it retunes the whole
library at once.
@theme { --radius: 0.25rem; }rounded-lg is --radius exactly; the steps either side are multiples of it, which is what keeps
a square-cornered theme square at every step. A button is the exception — its corner is half its own
height, so it stays a capsule however --radius is set. See Sizing.
Reading a colour from JavaScript
A class cannot express a literal (#EC4899) and cannot reach an SVG paint prop like a gradient's
stopColor. For a prop that needs a colour value, use useThemeColor.
import { useThemeColor } from "@delacour/react-native-ui/hooks/use-theme-color";
const destructive = useThemeColor("destructive");Size is a class, colour is a token
The asymmetry is deliberate. Tailwind's spacing scale genuinely owns 14/16/18/20/22/24/32, so a
class is the better name for those. A colour class could not express a literal or reach an SVG
paint prop, and useThemeColor already covers both — so a colour-class path would leave two
colour systems in the package rather than replacing one.
Theming a navigator
A navigator paints its own chrome from its own theme, and nothing connects that to these tokens.
expo-router mounts its NavigationContainer with no theme, so React Navigation's light
default stands however dark your app is. On iOS the native stack hands colors.background to the
UINavigationController's view — the slab visible between cards during a push, and at their
rounded corners. That layer has no escape hatch: a contentStyle in screenOptions cannot reach
it. Only a theme fixes it.
import { NavigationTheme } from "@delacour/react-native-ui/expo/navigation-theme";
<DelacourProvider>
<NavigationTheme>
<Stack />
</NavigationTheme>
</DelacourProvider>;On a different navigator, use the hook directly. It returns plain values, not a Theme — the
library imports nothing from a navigation package.
import { useNavigationTheme } from "@delacour/react-native-ui/hooks/use-navigation-theme";
const { dark, ...colors } = useNavigationTheme();
<NavigationContainer theme={{ ...DefaultTheme, dark, colors: { ...DefaultTheme.colors, ...colors } }}>Spread them over the framework's own base theme, which also supplies fonts — a platform-specific
shape that would drift if restated here.
dark comes from Uniwind's active theme, never React Native's useColorScheme. An app that lets
the user force light or dark against the system setting would otherwise theme its navigator the
wrong way round.
Generating one
If you would rather build a palette than paste one, Theme composes seven orthogonal
axes — style, radius, base colour, accent, chart hues and two typefaces — and shows the theme.css
they add up to, in exactly the shape init ships. Copy it over yours; there is nothing to run. A
second tab holds shadcn's globals.css for a web app that shares the theme. Each option is a link,
so the whole theme is the code in the URL: share it, bookmark it, or open it on a phone.
The playground app has the same axes on its own Theme screen, where they repaint a real app as you go rather than a specimen. Its footer opens that page with the configuration already in the URL, which is the way a theme built on a device becomes a file.