Themvora + shadcn/ui
@themvora/shadcn
Drive shadcn/ui components with Themvora semantic tokens via a CSS-variable adapter that stays in sync at runtime.
Pick your framework
The adapter writes shadcn/ui's own CSS custom properties from Themvora semantic tokens, so shadcn/ui's classes and components follow the active theme. Pick the framework you are building in — every snippet below follows that choice.
Installation
Two installs. The first is shadcn/ui's own — see its installation guide if you need more than this. The second is Themvora, and it names the runtime package for React.
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button cardpnpm add @themvora/core @themvora/react @themvora/shadcnHow it works
Themvora writes shadcn/ui's own CSS custom properties from its tokens, and shadcn/ui's stylesheet consumes them. Nothing is rebuilt on a theme change — the variables change and the styles follow. That is why this adapter is a hook that registers a writer rather than a provider component, and why it also needs its stylesheet imported.
Quick Start
shadcn/ui wired up for React. Switch the picker above and everything below changes with it.
import { useThemeRuntime } from "@themvora/react";
import { useShadcnTheme } from "@themvora/shadcn/react";
export function InstallAdapter() {
useShadcnTheme(useThemeRuntime());
return null;
}Mount the provider
Themvora's provider owns the active theme. Drop this at your app root — everything below it can read and change the theme, and the bridge above picks up each change automatically.
import { ThemeProvider } from "@themvora/react";
import "@themvora/shadcn/shadcn.css";
import { App as Page } from "./Page";
import { InstallAdapter } from "./components/InstallAdapter";
export default function App() {
return (
<ThemeProvider defaultTheme="plum-light" initialMode="system">
<InstallAdapter />
<Page />
</ThemeProvider>
);
}Use shadcn/ui components
import "@themvora/shadcn/shadcn.css";
export function Example() {
return (
<div className="card">
<h2>Themvora + shadcn/ui</h2>
<button className="primary">Primary action</button>
</div>
);
}These are ordinary shadcn/ui components. No Themvora-specific props are required — the theme reaches them through shadcn/ui's own mechanism.
Theme switching
The point of an adapter is that this is the only code that changes — the components stay exactly as they are.
import { useTheme } from "@themvora/react";
export function ThemeControls() {
const { setMode, toggleTheme } = useTheme();
return (
<>
<button type="button" onClick={() => toggleTheme()}>Toggle</button>
<button type="button" onClick={() => setMode("light")}>Light</button>
<button type="button" onClick={() => setMode("dark")}>Dark</button>
</>
);
}Before and after
You own the theme object. You choose the colours, keep them in step with your design tokens by hand, write the dark variant yourself, and rebuild the object whenever anything changes. Every new token has to be added twice — once to your design system and once to shadcn/ui.
Themvora owns the theme. The adapter derives the shadcn/uitheme from Themvora's tokens and rebuilds it whenever the active theme changes — switching family or mode, or a persisted preference resolving on load. There is no second place to update, because there is no second theme.
Your components stay shadcn/ui components. Themvora becomes the source of the active theme.
A complete example
Everything together for React. Paste it in and it runs.
import { ThemeProvider, useThemeRuntime } from "@themvora/react";
import { useShadcnTheme } from "@themvora/shadcn/react";
import "@themvora/shadcn/shadcn.css";
import { Example } from "./Example";
// No provider to mount: the hook registers the adapter, which writes
// shadcn/ui's own CSS variables. The stylesheet import above applies them.
function ThemeZone() {
useShadcnTheme(useThemeRuntime());
return <Example />;
}
export default function App() {
return (
<ThemeProvider defaultTheme="plum-light" initialMode="system">
<ThemeZone />
</ThemeProvider>
);
}What gets mapped
Themvora owns the meaning; the adapter owns the vocabulary.
| Themvora | shadcn/ui |
|---|---|
| background | --background |
| foreground | --foreground |
| card | --card |
| cardForeground | --card-foreground |
| primary | --primary |
| primaryForeground | --primary-foreground |
| secondary | --secondary |
| muted | --muted |
| mutedForeground | --muted-foreground |
| accent | --accent |
| destructive | --destructive |
| border | --border |
| ring | --ring |
Include the CSS
The adapter ships a stylesheet that maps its component variables (e.g. --primary for shadcn/ui, --bs-primary for Bootstrap) to the themvora --theme-color-* tokens. Import it once — anywhere — and the library's components will respond to every theme change automatically.
import "@themvora/shadcn/shadcn.css";What's Available
@themvora/shadcn ships 6 exports in 2 categories. Click any export to reveal what it does and how to use it.
Adapter
Options
Use Cases
The important features in practice — copy any of these straight into your app.
Mount the adapter on the runtime
Register the adapter so every theme change repaints --shadcn-* variables.
import { createThemeRuntime } from "@themvora/core";
import { createShadcnAdapter } from "@themvora/shadcn";
const runtime = createThemeRuntime();
runtime.adapters.use(createShadcnAdapter());
// Keep the handle to dispose later:
// const handle = runtime.adapters.use(createShadcnAdapter());
// handle.dispose();Switch tone at runtime
The adapter keeps variables in sync as the active family changes.
// The adapter is already registered on the runtime.
// Switch the family and the adapter repaints --shadcn-* variables:
runtime.selection.setFamily("mint");
runtime.selection.setMode("dark");More Examples
Static variable maps for build-time CSS, and manual adapter lifecycle control.
import { createShadcnVariables } from "@themvora/shadcn";
import { getBuiltInThemes } from "@themvora/core";
// These factories read `tokens` as-is — pass the token set, not a theme and
// not a `{ family, mode }` pair. Built-in themes ship a complete token set.
const [theme] = getBuiltInThemes();
const css = createShadcnVariables(theme.tokens!);
// css -> { "--background": "#f8fafc", "--foreground": "#0f172a", … }API Reference
Adapter
| Export | Description |
|---|---|
createShadcnAdapter(options) | Installs a tagged :root style element whose --* variables (colors, radius, border, shadow, typography) update as the theme changes. |
createShadcnVariables(tokens) | Deprecated — prefer createShadcnAdapter. Generates the shadcn CSS-variable map from a token set (theme.tokens), without touching the DOM. Passing a theme definition yields empty values. |
useShadcnTheme(runtime, options?) | React hook (from @themvora/shadcn/react) that installs and disposes the adapter onto an explicit runtime. Call once in your app root. |
injectShadcnCSS() | Idempotent, SSR-safe helper that injects the shadcn compatibility stylesheet. Called automatically by the hook. |
Options
| Export | Description |
|---|---|
CreateShadcnAdapterOptions | Accepts strategy to hand a custom token→variable mapping to the adapter. |
ShadcnAdapterOptions | Typed options for how semantic tokens map to shadcn CSS custom properties. |