Warning
Tokenami is still in early development. You might find bugs or missing features. Before reporting issues, please check our existing issues first.
- Quick start
- Core concepts
- Styling
- Design systems
- Advanced usage
- TypeScript integration
- Troubleshooting
- Community
Jump right in with our vite starter, or configure your own project. Tokenami offers a CLI tool for generating static styles, a ~2.5kb CSS utility for authoring your styles, and a TypeScript plugin to enhance the developer experience.
Install using your package manager of choice:
npm install -D tokenami && npm install @tokenami/css
Then initialise your project:
npx tokenami init
{
"include": [".tokenami/tokenami.env.d.ts", "src"],
"compilerOptions": {
"plugins": [{ "name": "tokenami" }]
}
}
- Make sure your editor uses the workspace TypeScript version (see VS Code docs).
- For VS Code–based editors, enable string suggestions and check our autocomplete tuning guide.
npx tokenami --output ./public/styles.css --watch
import { css } from '@tokenami/css';
function Page() {
return <h1 style={css({ '--margin-top': 0, '--margin-bottom': 5 })}>Hello, World!</h1>;
}
Tokenami is built around a few key ideas:
- Turn any CSS property into a variable by adding
--
(e.g.--padding
) - Add selectors with underscores (e.g.
--hover_padding
) - Add breakpoints the same way (e.g.
--md_padding
) - Combine selectors and breakpoints (e.g.
--md_hover_padding
) - Use
---
(triple dash) for custom CSS variables
The React team no longer recommends CSS-in-JS solutions that inject styles at runtime. Instead, they suggest:
[...] use
<link rel="stylesheet">
for static styles and plain inline styles for dynamic values. E.g.<div style={{...}}>
In other words—write CSS like we used to. But what about the benefits CSS-in-JS gave us?
Read more
Some CSS-in-JS tools already extract static styles into .css
files, but they often need bundler setup and have build-time limitations.
Developers use them for their design systems despite the learning curve, because they want:
- Type-checked tokens with autocomplete
- Enforced theme constraints to ensure consistency
- Style deduplication and critical path CSS
- Scoped, conflict-free styles
- Composable building blocks
Tailwind CSS offers a different approach:
- Atomic utility classes to limit stylesheet growth
- Editor extensions that suggest theme values
- Statically generated styles with a simple CLI, no bundler needed
- Quick prototyping using inline classes
But when building a design system, Tailwind has drawbacks:
- Removing a token in the theme does not show redundant usage in code
- Developers must memorise many class names (see Tailwind Cheatsheet)
- Style composition can create specificity conflicts (solved with third-party tools like tailwind-merge)
- Styling inline is not always ideal, leading to third-party tools like cva
- Classes must be written as complete unbroken strings making dynamic styling trickier
- Debugging is harder because styles are spread across many atomic classes
Tokenami isn't another design system. It's a toolkit for building your own.
By replacing atomic classes with atomic CSS variables and providing a unified set of tools, Tokenami makes it simple to create portable, type-safe design systems with tokens at the heart of your CSS.
Benefits include:
- Simple naming convention — turn any CSS property into a variable (
padding
→--padding
) - Smaller stylesheet — one variable-driven rule instead of dozens of utility classes
- Single source of truth — a config file defines and enforces your design tokens
- Smart authoring — autocomplete + type safety built into your workflow
- No specificity wars — a tiny first-class
css
utility handles safe composition - Dynamic by default — pass props directly into tokens (
--color: props.color
) - Shorthand tokens — define aliases like
--p
for padding - Expressive selectors — custom selectors for nesting and descendant rules
- Developer-friendly debugging — easier inspection in devtools (coming soon)
- Zero bundler friction — static styles with no bundler integration
Tip
Want to skip theme setup? Use our official design system which comes with dark mode, fluid typography, RTL support, and more.
Tokenami relies on your theme to provide design system constraints. Create one in .tokenami/tokenami.config
:
export default createConfig({
theme: {
color: {
'slate-100': '#f1f5f9',
'slate-700': '#334155',
'sky-500': '#0ea5e9',
},
radii: {
rounded: '10px',
circle: '9999px',
none: 'none',
},
},
});
Name your theme groups and tokens however you like. These names become part of your CSS variables.
Use the modes
key to define multiple themes. Choose any names for your modes. Tokens that are shared across themes should be placed in a root
object:
export default createConfig({
theme: {
modes: {
light: {
color: {
primary: '#f1f5f9',
secondary: '#334155',
},
},
dark: {
color: {
primary: '#0ea5e9',
secondary: '#f1f5f9',
},
},
},
root: {
radii: {
rounded: '10px',
circle: '9999px',
none: 'none',
},
},
},
});
This creates .theme-light
and .theme-dark
classes. Add them to your page to switch themes.
Customise the theme selector using the themeSelector
config:
export default createConfig({
themeSelector: (mode) => (mode === 'root' ? ':root' : `[data-theme=${mode}]`),
});
Tokenami uses a grid system for spacing. When you pass a number to properties like padding and margin, it multiplies that number by your grid value. For example, with a grid of 4px
, using --padding: 2
adds 8px
of padding.
By default, the grid is set to 0.25rem
. You can change it in your config:
export default createConfig({
grid: '10px',
// ... rest of your config
});
Use arbitrary selectors to prototype quickly:
<div
style={css({
'--{&:hover}_color': 'var(--color_primary)',
'--{&:has(:focus)}_border-color': 'var(--color_highlight)',
'--{&[data-state=open]}_border-color': 'var(--color_primary)',
// use underscore for spaces in your selector
'--{&_p}_color': 'var(--color_primary)',
})}
/>
They can be used to style the current element, and its descendants only.
You can avoid TypeScript errors for one-off inline values by using a triple-dash fallback.
<div style={css({ '--padding': 'var(---, 20px)' })} />
This prevents TypeScript errors and sets padding to 20px
. Tokenami intentionally adds friction to the developer experience here. This is to encourage sticking to your theme guidelines and to help you quickly spot values in your code that don't.
The css
utility is used to author your styles and helps with overrides and avoiding specificity issues. Use css
for inline styles.
Pass your base styles as the first parameter, then any overrides:
function Button(props) {
return (
<button
{...props}
style={css(
{ '--padding': 4 }, // Base styles
props.style // Overrides
)}
/>
);
}
Add conditional styles as extra parameters. The last override wins:
function Button(props) {
const disabled = props.disabled && {
'--opacity': 0.5,
'--pointer-events': 'none',
};
return (
<button
{...props}
style={css(
{ '--padding': 4 }, // Base styles
disabled, // Conditional styles
props.style // Props override
)}
/>
);
}
The css.compose
API helps you build reusable components with variants. Styles in the compose block are extracted into your stylesheet and replaced with a class name to reduce repetition in your markup.
Here's a basic example:
const button = css.compose({
'--background': 'var(--color_primary)',
'--hover_background': 'var(--color_primary-dark)',
});
function Button(props) {
const [cn, css] = button();
return <button {...props} className={cn(props.className)} style={css(props.style)} />;
}
Output:
<button class="tk-abc">click me</button>
The variants
object lets you define different style variations:
const card = css.compose({
'--border-radius': 'var(--radii_rounded)',
'--color': 'var(--color_white)',
'--font-size': 'var(--text-size_sm)',
variants: {
color: {
blue: { '--background-color': 'var(--color_blue)' },
green: { '--background-color': 'var(--color_green)' },
},
size: {
small: { '--padding': 2 },
large: { '--padding': 6 },
},
},
});
Use multiple variants together:
function Card(props) {
const [cn, css] = card({ color: 'blue', size: 'large' });
return <div {...props} className={cn(props.className)} style={css(props.style)} />;
}
Variants are treated like overrides, so appear inline:
<div class="tk-abc" style="--background-color: var(--color_blue); --padding: 6;">boop</div>
Use includes
to combine styles from multiple components or css
utilities.
// Reusable focus styles (will appear inline)
const focusable = css({
'--focus_outline': 'var(--outline_sm)',
'--outline-offset': 'var(--outline-offset_sm)',
});
// Base button styles (composed so will be extracted into stylesheet)
const button = css.compose({
'--background': 'var(--color_primary)',
'--color': 'var(--color_white)',
'--padding': 4,
});
// New button that includes both
const tomatoButton = css.compose({
includes: [button, focusable],
'--background': 'var(--color_tomato)',
});
Conflicting styles (e.g. --background
) are moved inline to override:
<button
class="tk-abc"
style="--focus_outline: var(--outline_sm); --outline-offset: var(--outline-offset_sm); --background: var(--color_tomato);"
>
click me
</button>
Tokenami eases the friction of creating portable design systems, whether you're building your own or using our official one.
Our official design system comes with:
- Global reset based on Preflight from Tailwind
- Radix UI colours with automatic dark mode
- Fluid spacing and font sizes for responsive design
- Right-to-left support built in (
padding-left
becomespadding-inline-start
etc.) - Short aliases for common properties (e.g.
--p
for padding)
Follow the @tokenami/ds
docs to get started.
Create a shared Tokenami config + stylesheet package, and publish it for projects to consume. If consumer is using Tokenami also, they should include your design system in their config:
import designSystemConfig from '@acme/design-system';
import { createConfig } from '@tokenami/css';
export default createConfig({
...designSystemConfig,
include: ['./app/**/*.{ts,tsx}', 'node_modules/@acme/design-system/tokenami.css'],
});
Projects that consume a Tokenami design system do not need to be using Tokenami themselves though. If they're not using Tokenami, they can reference their stylesheet after the design system stylesheet and their styles will override accordingly.
Provide global styles in your config to include them as part of your design system.
export default createConfig({
globalStyles: {
'*, *::before, *::after': {
boxSizing: 'border-box',
},
body: {
fontFamily: 'system-ui, sans-serif',
lineHeight: 1.5,
margin: 0,
padding: 0,
},
},
});
Define your breakpoints in the responsive
config:
export default createConfig({
responsive: {
md: '@media (min-width: 700px)',
lg: '@media (min-width: 1024px)',
'md-self': '@container (min-width: 400px)', // Container queries work too!
},
});
Use them by adding the breakpoint name before any property:
<div
style={css({
'--padding': 2, // Base padding
'--md_padding': 4, // Padding at medium breakpoint
'--lg_padding': 6, // Padding at large breakpoint
'--md-self_padding': 8, // Padding when container is medium
})}
/>
Add keyframes to your config and reference them in your theme:
export default createConfig({
keyframes: {
wiggle: {
'0%, 100%': { transform: 'rotate(-3deg)' },
'50%': { transform: 'rotate(3deg)' },
},
},
theme: {
anim: {
wiggle: 'wiggle 1s ease-in-out infinite',
},
},
});
Apply the animation to an element:
<div style={css({ '--animation': 'var(--anim_wiggle)' })} />
Tokenami has some advanced features that can help you build more powerful design systems.
Some common selectors are included, but you can configure your own. Use the ampersand (&
) to mark where the current element's selector should be injected:
export default createConfig({
selectors: {
'parent-hover': '.parent:hover > &',
// Nested selectors work too
hover: ['@media (hover: hover) and (pointer: fine)', '&:hover'],
},
});
Use them in your components:
<div className="parent">
<img src="..." alt="" />
<button style={css({ '--parent-hover_color': 'var(--color_primary)' })} />
</div>
Aliases allow you to create shorthand names for properties. When using custom aliases, the css
utility must be configured to ensure conflicts are resolved correctly across component boundaries.
// css.ts
import { createCss } from '@tokenami/css';
import config from '../.tokenami/tokenami.config';
export const css = createCss(config);
export default createConfig({
aliases: {
p: ['padding'],
px: ['padding-inline'],
py: ['padding-block'],
size: ['width', 'height'],
},
});
<div style={css({ '--p': 4, '--px': 2, '--size': '100%' })} />
Tokenami maps your properties to some default theme keys out of the box. For example, --border-color
accepts tokens from your color
theme object, while --padding
works with your grid system. You can customise these mappings in the properties
key:
export default createConfig({
theme: {
container: {
half: '50%',
full: '100%',
},
pet: {
cat: '"🐱"',
dog: '"🐶"',
},
},
properties: {
width: ['grid', 'container'],
height: ['grid', 'container'],
content: ['pet'],
},
});
With this configuration, passing var(--container_half)
to a content
property would error because container
does not exist in its property config, but var(--pet_dog)
would be allowed:
<div
style={css({
'--width': 'var(--container_half)', // Works ✅
'--content': 'var(--pet_cat)', // Works ✅
'--content': 'var(--container_half)', // Error ❌
})}
/>
Create your own properties for design system features. For example, make gradient properties that use your colour tokens by adding them to the customProperties
key:
export default createConfig({
theme: {
color: {
primary: '#f1f5f9',
secondary: '#334155',
},
gradient: {
// use your custom properties to configure the gradient
radial: 'radial-gradient(circle at top, var(--gradient-from), var(--gradient-to) 80%)',
},
},
properties: {
'background-image': ['gradient'],
},
customProperties: {
'gradient-from': ['color'],
'gradient-to': ['color'],
},
});
Then use them as follows:
<div
style={css({
'--background-image': 'var(--gradient_radial)',
'--gradient-from': 'var(--color_primary)',
'--gradient-to': 'var(--color_secondary)',
})}
/>
Use the Variants
type to extend your component props with the available variants:
import { type Variants } from '@tokenami/css';
type ButtonElementProps = React.ComponentPropsWithoutRef<'button'>;
interface ButtonProps extends ButtonElementProps, Variants<typeof button> {}
Components styled with the css
utility can use TokenamiStyle
to type their style prop if you want it to accept Tokenami properties.
import { type TokenamiStyle, css } from '@tokenami/css';
interface ButtonProps extends TokenamiStyle<React.ComponentProps<'button'>> {}
function Button(props: ButtonProps) {
return <button {...props} style={css({}, props.style)} />;
}
Now you can pass Tokenami properties with type checking:
<Button style={{ '--padding': 4 }} />
Use TokenValue
to get a union of CSS variable tokens based on your theme.
Given this theme:
export default createConfig({
theme: {
color: {
'slate-100': '#f1f5f9',
'slate-700': '#334155',
},
radii: {
rounded: '10px',
circle: '9999px',
},
},
});
It will output the following types:
import { type TokenValue } from '@tokenami/css';
type Color = TokenValue<'color'>; // var(--color_slate-100) | var(--color_slate-700)
type Radii = TokenValue<'radii'>; // var(--radii_rounded) | var(--radii_circle)
Tokenami uses widened types during development for better performance. When you run tsc
in the command line, it uses these widened types and won't show Tokenami type errors.
For accurate type checking in CI, run both commands:
tokenami check; tsc --noEmit
Common questions and how to solve them. If you need additional support or encounter any issues, please don't hesitate to join the discord server.
Tokenami applies your authored styles directly to the style
attribute to minimise runtime overhead. Since the style
attribute doesn't support media queries or pseudo-selectors, we use CSS variables to enable them. Making everything a CSS variable simplifies the learning curve.
CSS variables also have lower specificity than direct CSS properties in the style
attribute. This allows overriding Tokenami's styles by adding a stylesheet after Tokenami's when needed.
Tip
Don't worry about typing extra dashes! Just type bord
and Tokenami's intellisense will help autocomplete it in the right format.
VS Code won't suggest completions for partial strings by default. This prevents Tokenami from updating its suggestions. To fix, add the following to .vscode/settings.json
:
{
"editor.quickSuggestions": {
"strings": true
}
}
BEFORE | AFTER |
---|---|
![]() |
![]() |
Some editors that Tokenami integrates with (such as VS Code and Cursor) support VS Code–style configuration settings. If you feel Tokenami's completions are slow or match on irrelevant suggestions, you can refine IntelliSense behaviour by adding the following settings to your workspace .vscode/settings.json
:
{
"editor.suggest.filterGraceful": false,
"editor.suggest.matchOnWordStartOnly": true
}
- "editor.suggest.filterGraceful": false: Filters out loosely related suggestions so closer matches are shown.
- "editor.suggest.matchOnWordStartOnly": true: Redcues noise by prioritising suggestions that begin with your typed characters.
Tokenami currently works with:
![]() React |
![]() Preact |
![]() Vue |
![]() SolidJS |
![]() Qwik |
---|---|---|---|---|
✅ | ✅ | ✅ | ✅ | ✅ |
We're still in early development and plan to support more libraries in the future.
Tokenami relies on cascade layers, so it works in browsers that support @layer
:
![]() Edge |
![]() Firefox |
![]() Chrome |
![]() Safari |
![]() iOS Safari |
![]() Opera |
---|---|---|---|---|---|
99+ | 97+ | 99+ | 15.4+ | 15.4+ | 86+ |
Tokenami is officially supported in the following editors:
You can use browserslist to add autoprefixing to your CSS properties in the generated CSS file. However, Tokenami currently doesn't support vendor-prefixed values, which is being tracked in this issue.
Important
Tokenami does not support browsers below the listed supported browser versions. We recommend using "browserslist": ["supports css-cascade-layers"]
if you're unsure.
Before raising a bug, please check if it's already in our todo list. Need help? Join our Discord server.
- Paweł Błaszczyk (@pawelblaszczyk_)
A big thanks to:
- Tailwind V3 for inspiring many of Tokenami's features
- Stitches for variants inspiration
- CSS Hooks for custom selectors inspiration
- Lightning CSS for generating the Tokenami stylesheet
Please do check out these projects if Tokenami isn't quite what you're looking for.