How to use themes in your project
Wire generated themes into next-themes and Tailwind
Enable usage of themes
Firstly, you should enable usage of themes in the config. You can enable usage of themes for colors and effects.
Set an array of allowedThemes and a defaultTheme in Config -> styles:
module.exports = {
// ...
styles: {
exportPath: './theme',
allowedThemes: ['light', 'dark'],
defaultTheme: 'light',
// ...
},
};For more information follow Configuration.
Launch the extractor
After a successful launch of the extractor you will see the created files (click a file to preview its content):
/* eslint-disable max-lines */
/* eslint-disable @typescript-eslint/naming-convention */
// THIS FILE IS GENERATED AUTOMATICALLY. DON'T CHANGE IT.
export const DEFAULT_THEME = 'light';
export const THEMES = ['dark', 'light'] as const;
export type Theme = (typeof THEMES)[number];Install the next-themes package in your project
Generated files can be used with next-themes — a great solution for switching themes.
Include received themes in next-themes
Inside app.tsx you should add the following code:
import { THEMES, DEFAULT_THEME } from '@sh/ui/theme/themes-list';
<ThemeProvider attribute="data-theme" defaultTheme={DEFAULT_THEME} themes={[...THEMES]}>
{children}
</ThemeProvider>;Connect the generated theme to Tailwind
These examples target Tailwind CSS 4. The recommended way is to keep a tailwind.config.ts and load it from CSS with the @config directive — it stays a single source of truth and picks up the generated tokens automatically. Mapping the variables inline with @theme inline also works, but it's extra manual work (you list and maintain every token by hand), so reach for it only if you specifically want a config-less setup.
This is the recommended, more idiomatic setup. Keep a tailwind.config.ts that imports the generated with-vars.ts files, and load that config from your CSS with the @config directive — no need to re-declare tokens by hand.
import { colors } from './theme/colors/with-vars';
import { effects } from './theme/effects/with-vars';
/** @type {import('tailwindcss').Config} */
const config = {
theme: {
...effects,
colors: {
transparent: 'transparent',
current: 'currentColor',
...colors,
},
},
};
export default config;Import with-vars.ts, not index.ts. with-vars.ts references the CSS variables so themes
switch correctly; index.ts contains raw data from Figma and is only kept for legacy code.
Then, in your global stylesheet, import Tailwind, point it at the config with @config, and import the generated vars.css files (the root theme and every theme):
@import 'tailwindcss';
@config './tailwind.config.ts';
/* generated variables — :root + one selector per theme */
@import '@sh/ui/theme/colors/vars.css';
@import '@sh/ui/theme/colors/light/vars.css';
@import '@sh/ui/theme/colors/dark/vars.css';
@import '@sh/ui/theme/effects/vars.css';
@import '@sh/ui/theme/effects/light/vars.css';
@import '@sh/ui/theme/effects/dark/vars.css';The @config path is resolved relative to the CSS file — adjust ./tailwind.config.ts to wherever your config actually lives (for example ../../tailwind.config.ts).
Prefer a config-less setup? You can expose the tokens to Tailwind directly in CSS with a @theme inline block. The trade-off is extra manual work: you have to list every generated variable by hand and keep that list in sync as the design system grows — which is why the tailwind.config.ts approach above is recommended.
@import 'tailwindcss';
/* generated variables — :root + one selector per theme */
@import '@sh/ui/theme/colors/vars.css';
@import '@sh/ui/theme/colors/light/vars.css';
@import '@sh/ui/theme/colors/dark/vars.css';
@import '@sh/ui/theme/effects/vars.css';
@import '@sh/ui/theme/effects/light/vars.css';
@import '@sh/ui/theme/effects/dark/vars.css';
/* map generated variables to Tailwind utilities */
@theme inline {
--color-bg100: var(--sh-bg100);
--color-bg200: var(--sh-bg200);
--color-txt100: var(--sh-txt100);
--color-txt800: var(--sh-txt800);
--shadow-dp100: var(--sh-dp100);
--shadow-dp250: var(--sh-dp250);
--shadow-dp900: var(--sh-dp900);
--shadow-dp1000: var(--sh-dp1000);
}This gives you utilities like bg-bg100, text-txt800 and shadow-dp100.
The inline keyword matters. It makes the utilities resolve to var(--sh-*) at runtime, so switching data-theme (via next-themes) re-resolves every color on its own — no dark: variants required. Without inline, Tailwind would bake in the current value and themes would stop switching.
Finish
Now you can use Tailwind classes as usual.
To validate class names against your theme (catch typos and unknown utilities from the generated
tokens), add
eslint-plugin-tailwindcss to
your ESLint config. It lints Tailwind class names — including the custom utilities generated from
your Figma variables — so invalid classes are caught during development.