Tailwind CSS v4 examples
Each export in @themvora/tailwind, with the problem it solves and the code that solves it.
@themvora/tailwindTailwind CSS v4v2.0.0Stable← Back to the Tailwind CSS v4 guide
The Tailwind integration is build-time only: it maps Themvora's semantic tokens onto Tailwind's @theme variables so bg-primary, text-muted-foreground and rounded-lg resolve to the active theme. There is no provider here — a runtime for your framework (or <themvora-provider> for plain HTML) does the theming, and Tailwind reads the variables it produces.
That split is why this page is short on JavaScript: the interesting work is one @import, one custom variant, and knowing which class names the tokens generate.
1One import, one dark variant
@import "tailwindcss";
/* Maps Themvora tokens onto Tailwind's @theme variables:
--color-*, --radius-*, --spacing-*, --font-*, --shadow-* */
@import "@themvora/tailwind";
/* Scope `dark:` to the class Themvora maintains on <html>. */
@custom-variant dark (&:where(.dark, .dark *));<article class="bg-card text-card-foreground border border-border rounded-lg p-6 shadow-md">
<h2 class="text-2xl font-semibold">Themed by tokens</h2>
<p class="text-muted-foreground">No hard-coded palette.</p>
<button class="bg-primary text-primary-foreground rounded-md px-4 py-2">
Primary action
</button>
</article>You still need a Themvora runtime — @themvora/react, -vue, -svelte, -solid, -angular or -web — to apply a theme. Tailwind alone only generates the class names.
2Tokens instead of hex values
Style a component without a second palette
A component ends up with bg-[#6366f1] because the token name was not obvious, and it looks wrong in every theme but one.
@import "@themvora/tailwind"Dark-mode variantexport function StatCard({ label, value }: { label: string; value: string }) {
return (
<article className="rounded-lg border border-border bg-card p-6 text-card-foreground shadow-md dark:border-border">
<h3 className="text-sm text-muted-foreground">{label}</h3>
<strong className="text-2xl">{value}</strong>
</article>
);
}The import generates a utility per semantic token, so the semantic name is always available. The custom variant makes dark: follow the active theme rather than the OS. The component repaints with every theme change and needs no per-theme classes.
Watch out: Token names keep the casing of the token key: text-card-foreground, not text-card-foreground invented from a different key. Check the token reference when a utility does not resolve.
Make dark: work inside a scoped panel
A scoped dark panel renders with light dark: utilities, because the class-based variant is only on <html>.
Dark-mode variant<themvora-scope><themvora-scope theme="cocoa-dark">
<div class="bg-card text-card-foreground dark:bg-background">
Both classes follow the scope, not the page.
</div>
</themvora-scope>&:where(.dark, .dark *) matches any ancestor carrying the class, and a dark scope adds it to its own wrapper — so the variant activates inside the scope without touching the page. dark: utilities inside the scoped panel follow the panel's theme, while the rest of the page keeps the global one.
3The stylesheet, the plugin, the variant
Integration
The CSS entry, the dark-mode variant, and the two helpers that keep Tailwind's expectations and Themvora's DOM contract in agreement.
@import "@themvora/tailwind"
One CSS import that maps every Themvora token group onto Tailwind's @theme variables: --color-*, --radius-*, --spacing-*, --font-* and --shadow-*. Tailwind then generates the matching utilities.
Every Tailwind v4 project using Themvora. It replaces a tailwind.config theme block entirely.
@import "tailwindcss";
@import "@themvora/tailwind";Utilities such as bg-primary, text-muted-foreground, rounded-lg and shadow-md resolve to the active theme's values and repaint when the theme changes.
Relatedtw-dark-varianttw-layers
Dark-mode variant
@custom-variant dark (&:where(.dark, .dark *)) scopes Tailwind's dark: variant to the .dark class Themvora maintains on the root element, instead of Tailwind's default prefers-color-scheme.
Always, when the theme is selected by Themvora rather than by the OS alone. Without it, dark: utilities follow the OS and disagree with a visitor who picked light explicitly.
@import "tailwindcss";
@import "@themvora/tailwind";
/* Follow the class Themvora writes, not the OS preference. */
@custom-variant dark (&:where(.dark, .dark *));<div class="bg-background text-foreground dark:bg-card">
Repaints with the active theme's mode.
</div>dark: utilities activate exactly when Themvora's active theme is dark — including inside a scoped subtree, because the variant matches any ancestor with the class.
Relatedtw-importtw-sync-dark
synchronizeDarkClass(theme)
A function that toggles the .dark class on the document root to match a theme definition's meta.mode.
You apply a theme yourself — from a custom script, a non-Themvora runtime, or a server response — and need Tailwind's dark: variant to follow. When Themvora's runtime is applying the theme, it already maintains the class.
import { synchronizeDarkClass } from "@themvora/tailwind";
import { getBuiltInThemes } from "@themvora/core";
const themes = getBuiltInThemes();
const dark = themes.find((theme) => theme.meta?.mode === "dark");
if (dark) {
// Keeps Tailwind's `dark:` variant in step with a theme applied by hand.
synchronizeDarkClass(dark);
}The root gains or loses .dark, so dark: utilities switch with it. It is a no-op on the server.
Relatedtw-dark-variant
CSS layers
The package ships theme.css, dark.css and preflight.css layers, so the token declarations, the dark-mode block and the reset can be imported independently or in a controlled order.
You need the layer files separately — a design system that owns its own reset, or a page that must load only the token layer.
/* The single `@import "@themvora/tailwind"` pulls in all three.
Import them yourself when you need a different order or a subset. */
@import "@themvora/tailwind/theme.css";
@import "@themvora/tailwind/dark.css";
/* Skip preflight.css when your own reset already owns base styles. */
/* @import "@themvora/tailwind/preflight.css"; */Imports compose in the order you write them, which is what decides whether your own rules can override a token declaration.
Relatedtw-import
Transition
Tailwind exposes the transition variables; the runtime owns the orchestration. There is no global transition block to write.
CSS transition variables
Themova exposes transition variables for Tailwind-aware styling, while the runtime performs the actual theme-change orchestration.
You want a class-driven transition on a specific element (a hover state, a panel) that matches the theme's timing rather than inventing a second duration.
/* Read the runtime's timing instead of hard-coding a duration. */
.panel {
transition:
background-color var(--theme-transition-duration, 300ms)
var(--theme-transition-easing, ease),
color var(--theme-transition-duration, 300ms)
var(--theme-transition-easing, ease);
}Your transitions read the same duration and easing the runtime uses, so a hover and a theme change feel like one system.
createTailwindPlugin({ themes, defaultTheme })
A plugin factory for projects that configure Tailwind in JavaScript rather than CSS. It registers Themvora tokens with Tailwind from a JS config.
A Tailwind v3-era config, or a build that generates its Tailwind config programmatically. In Tailwind v4 the CSS import is the shorter path.
import { createTailwindPlugin } from "@themvora/tailwind";
import { getBuiltInThemes } from "@themvora/core";
const themes = getBuiltInThemes();
export default {
plugins: [
createTailwindPlugin({ themes, defaultTheme: "mint-light" }),
],
};The tokens become Tailwind theme values at build time. The plugin is configuration only — it does not apply a theme at runtime.
Relatedtw-importtw-theme-css
themeCSS
The Tailwind-compatible @theme variable block generated from Themvora tokens, exported as a string and imported through the package's CSS entry.
You need the generated block as a value — to write it into a file during a build, or to inline it into a server-rendered stylesheet.
import { writeFileSync } from "node:fs";
import { themeCSS } from "@themvora/tailwind";
writeFileSync("src/generated/theme.css", themeCSS);You get the same declarations the CSS entry provides, in a form you can place yourself.
4The complete stylesheet
The complete stylesheet
Everything the Tailwind side needs, in the order it has to appear: Tailwind itself, the Themvora token mapping, then the variant that ties dark: to the class the runtime maintains.
@import "tailwindcss";
@import "@themvora/tailwind";
@custom-variant dark (&:where(.dark, .dark *));
/* Your own base styles read the same variables, so they repaint too. */
body {
background: var(--theme-color-background);
color: var(--theme-color-foreground);
}Not available in this integration
These exports exist in other integrations but not in @themvora/tailwind. Listing them is more useful than inventing an example: if you need one of them here, the workaround is named.
This package is build-time only. Install a runtime adapter (@themvora/react, -vue, -svelte, -solid, -angular or -web) to apply a theme; Tailwind only generates class names from the tokens.