https://github.com/tokenami/tokenami
CSS-in-JS reinvented for scalable, typesafe design systems. A modern approach to just-in-time atomic CSS using CSS variables—no bundler required.
https://github.com/tokenami/tokenami
Last synced: 8 months ago
JSON representation
CSS-in-JS reinvented for scalable, typesafe design systems. A modern approach to just-in-time atomic CSS using CSS variables—no bundler required.
- Host: GitHub
- URL: https://github.com/tokenami/tokenami
- Owner: tokenami
- License: mit
- Created: 2023-06-19T12:03:37.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-08-29T17:25:19.000Z (8 months ago)
- Last Synced: 2025-08-29T19:14:03.028Z (8 months ago)
- Language: TypeScript
- Homepage: https://tokenami.com
- Size: 2.43 MB
- Stars: 969
- Watchers: 11
- Forks: 11
- Open Issues: 20
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
- awesome-typesafe - tokenami/tokenami - Atomic CSS in the style attribute. Type-safe static styles with theming, responsive variant support, and no bundler integration. (**1. Libraries** / Styles)
README

CSS-in-JS Reinvented for Scalable, Typesafe Design Systems
A modern approach to just-in-time atomic CSS using CSS variables—no bundler required.

> [!Warning]
> Tokenami is still in early development. You might find bugs or missing features. Before reporting issues, please check our [existing issues](https://github.com/tokenami/tokenami/issues) first.
## Contents
- [Why Tokenami?](#user-content-why-tokenami)
- [Quick start](#user-content-quick-start)
- [Installation](#user-content-installation)
- [Basic setup](#user-content-basic-setup)
- [Core concepts](#user-content-core-concepts)
- [Theming](#user-content-theming)
- [Grid values](#user-content-grid-values)
- [Arbitrary selectors](#user-content-arbitrary-selectors)
- [Arbitrary values](#user-content-arbitrary-values)
- [Styling](#user-content-styling)
- [CSS utility](#user-content-css-utility)
- [CSS compose](#user-content-css-compose)
- [Variants](#user-content-variants)
- [Extending styles](#user-content-extending-styles)
- [Design systems](#user-content-design-systems)
- [Using the official system](#user-content-using-the-official-system)
- [Building your own system](#user-content-building-your-own-system)
- [Global styles](#user-content-global-styles)
- [Breakpoints](#user-content-breakpoints)
- [Animation](#user-content-animation)
- [Advanced usage](#user-content-advanced-usage)
- [Custom selectors](#user-content-custom-selectors)
- [Property aliases](#user-content-property-aliases)
- [Theming properties](#user-content-theming-properties)
- [Custom properties](#user-content-custom-properties)
- [TypeScript integration](#user-content-typescript-integration)
- [Utility types](#user-content-utility-types)
- [CI setup](#user-content-ci-setup)
- [Troubleshooting](#user-content-troubleshooting)
- [Why the CSS variable syntax?](#user-content-why-the-css-variable-syntax)
- [VSCode setup](#user-content-vscode-setup)
- [Supported libraries](#user-content-supported-libraries)
- [Supported browsers](#user-content-supported-browsers)
- [Supported editors](#user-content-supported-editors)
- [Browserslist](#user-content-browserslist)
- [Community](#user-content-community)
- [Contributing](#user-content-contributing)
- [Contributors](#user-content-contributors)
- [Credits](#user-content-credits)
## Why Tokenami?
The React team [no longer recommends](https://github.com/reactwg/react-18/discussions/110) CSS-in-JS solutions that inject styles. Instead, they suggest:
> [...] use [``](https://github.com/reactwg/react-18/discussions/108) for static styles and plain inline styles for dynamic values. E.g. `
`
In other words—write CSS like we used to. But what about the benefits CSS-in-JS gave us? Some CSS-in-JS tools extract static styles into `.css` files, but they often need [bundler setup](https://vanilla-extract.style/documentation/integrations/next/) and have [build-time limitations](https://panda-css.com/docs/guides/dynamic-styling).
Read more
Developers use these tools despite the learning curve because they want:
- Type checking and suggestions for design system tokens
- Style deduplication
- Critical path CSS
- Style scoping
- Composition without specificity conflicts
Tailwind CSS adopts a different strategy:
- Atomic CSS so styles have a cap on how large they can grow
- Statically generated styles with a simple CLI script, no bundler integration needed
- Quick prototyping with inline styles
- Editor tools suggest classes from your theme
On the flip side:
- Removing values from your theme won't flag redundant references
- We must memorise Tailwind's custom class names which spawns things like the [Tailwind Cheatsheet](https://tailwindcomponents.com/cheatsheet/)
- Specificity issues when composing unless we use third-party packages like [tailwind-merge](https://www.npmjs.com/package/tailwind-merge)
- Styling inline can be unpleasant to maintain, resulting in third-party packages like [cva](https://cva.style/docs)
- Classes must exist as [complete unbroken strings](https://tailwindcss.com/docs/content-configuration#dynamic-class-names)
- Debugging in dev tools is tricky because styles are spread across atomic classes
### Introducing Tokenami
Tokenami aims to improve some of these areas by using atomic CSS variables instead of atomic classes, and bringing all necessary tools under one roof. It features:
- Simple naming convention—convert any CSS property to a CSS variable (e.g. `padding` becomes `--padding`)
- Smaller stylesheet using atomic CSS variables (e.g. one `padding: var(--padding)` rule vs. many `.p-1`, `.p-2` classes)
- Config file for providing design system constraints
- Feature-rich intellisense when authoring styles
- Tiny `css` utility for variant support and composition without specificity conflicts
- Dynamic style support (e.g. `style={css({ '--color': props.color })}`)
- Aliasable properties (e.g. `style={css({ '--p': 4 })}` for padding)
- Custom selector support enabling descendant selectors
- Improved debugging experience in dev tools (coming soon)
- Static style generation
- No bundler integration needed
| Tokenami DX | HTML output |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|  |  |
## Quick start
Jump right in with our [vite starter](https://github.com/tokenami/tokenami-vite), or configure your own project. Tokenami offers a CLI tool for generating static styles, a [~2.5kb](https://bundlephobia.com/package/@tokenami/css) CSS utility for authoring your styles, and a TypeScript plugin to enhance the developer experience.
### Installation
Install using your package manager of choice:
```sh
npm install -D tokenami && npm install @tokenami/css
```
Then initialise your project:
```sh
npx tokenami init
```
### Basic setup
#### 1. Add Tokenami to your TypeScript config (`tsconfig.json` or `jsconfig.json`):
```json
{
"include": [".tokenami/tokenami.env.d.ts", "src"],
"compilerOptions": {
"plugins": [{ "name": "tokenami" }]
}
}
```
#### 2. Run the CLI script to watch files and build your CSS:
```sh
npx tokenami --output ./public/styles.css --watch
```
You can change where the CSS file is generated by changing the `--output` flag. By default, it uses `./public/tokenami.css`.
#### 3. Reference the CSS file in the `` of your document, and start styling with Tokenami properties:
Make sure your editor uses the workspace version of TypeScript. Check your editor's docs, like [VSCode's guide](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript). VSCode users should also [enable suggestions for strings](#user-content-vscode-setup).
```tsx
import { css } from '@tokenami/css';
function Page() {
return
Hello, World!
;
}
```
## Core concepts
Tokenami is built around a few key ideas:
- Turn any CSS property into a variable by adding `--` (e.g. `--padding`)
- Use `---` (triple dash) for custom CSS variables
- 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`)
### Theming
> [!Tip]
> Want to skip theme setup? Use our [official design system](https://github.com/tokenami/tokenami/blob/main/packages/ds/README.md) which comes with dark mode, fluid typography, RTL support, and more. Jump to [using the official system](#user-content-using-the-official-system).
Tokenami relies on your theme to provide design system constraints. Create one in `.tokenami/tokenami.config`:
```ts
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.
#### Multiple themes
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:
```ts
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:
```ts
export default createConfig({
themeSelector: (mode) => (mode === 'root' ? ':root' : `[data-theme=${mode}]`),
});
```
### Grid values
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:
```ts
export default createConfig({
grid: '10px',
// ... rest of your config
});
```
### Arbitrary selectors
Use arbitrary selectors to prototype quickly:
```tsx
```
They can be used to style the **current element, and its descendants** only.
### Arbitrary values
You can avoid TypeScript errors for one-off inline values by using a triple-dash fallback.
```tsx
```
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.
## Styling
### CSS utility
The `css` utility is used to author your styles and helps with overrides and avoiding specificity issues. Use `css` for inline styles.
#### Usage
Pass your base styles as the first parameter, then any overrides:
```tsx
function Button(props) {
return (
);
}
```
#### Overrides
Add conditional styles as extra parameters. The last override wins:
```tsx
function Button(props) {
const disabled = props.disabled && {
'--opacity': 0.5,
'--pointer-events': 'none',
};
return (
);
}
```
### CSS compose
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:
```tsx
const button = css.compose({
'--background': 'var(--color_primary)',
'--hover_background': 'var(--color_primary-dark)',
variants: {
size: {
small: { '--padding': 2 },
large: { '--padding': 6 },
},
},
});
function Button({ size = 'small', ...props }) {
const [cn, css] = button({ size });
return ;
}
```
#### Variants
The `variants` object lets you define different style variations:
```tsx
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:
```tsx
function Card(props) {
const [cn, css] = card({ color: 'blue', size: 'large' });
return
;
}
```
Variants are treated like overrides, so appear inline:
```html
boop
```
#### Extending styles
Use `includes` to combine styles from multiple components or `css` utilities. Conflicting styles (e.g. `--background`) are moved inline to ensure correct overrides:
```tsx
// Reusable focus styles (will appear inline)
const focusable = css({
'--focus_outline': 'var(--outline_sm)',
'--outline-offset': 'var(--outline-offset_sm)',
});
// Base button styles (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)',
});
```
Output:
```html
click me
```
## Design systems
Design systems help teams build consistent interfaces. Tokenami eases the friction of creating and consuming design systems, whether you're building your own or using our official one.
### Using the official system
Our [official design system](https://github.com/tokenami/tokenami/blob/main/packages/ds/README.md) comes with:
- Global reset based on [Preflight from Tailwind](https://github.com/tailwindlabs/tailwindcss/blob/next/packages/tailwindcss/preflight.css)
- [Radix UI colours](https://www.radix-ui.com/colors) with automatic dark mode
- [Fluid spacing and font sizes](https://utopia.fyi/) for responsive design
- Right-to-left support built in (`padding-left` becomes `padding-inline-start` etc.)
- Short aliases for common properties (e.g. `--p` for padding)
Follow [the `@tokenami/ds` docs](https://github.com/tokenami/tokenami/blob/main/packages/ds/README.md) to get started.
### Building your own system
Want to create your own portable design system? Create a shared Tokenami config and stylesheet package that projects can consume:
```tsx
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'],
});
```
### Global styles
Provide global styles in your config to include them as part of your design system.
```ts
export default createConfig({
globalStyles: {
'*, *::before, *::after': {
boxSizing: 'border-box',
},
body: {
fontFamily: 'system-ui, sans-serif',
lineHeight: 1.5,
margin: 0,
padding: 0,
},
},
});
```
### Breakpoints
Define your breakpoints in the `responsive` config:
```ts
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:
```tsx
```
### Animation
Add keyframes to your config and reference them in your theme:
```ts
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:
```tsx
```
## Advanced usage
Tokenami has some advanced features that can help you build more powerful design systems.
### Custom selectors
Some [common selectors](https://github.com/tokenami/tokenami/blob/main/packages/tokenami/stubs/tokenami.config.ts#L28) are built in, but you can add your own. Use the ampersand (`&`) to mark where the current element's selector should be injected:
```ts
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:
```tsx
```
### Property aliases
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.
#### 1. Create a file in your project to configure the utility. You can name this file however you like:
```ts
// css.ts
import { createCss } from '@tokenami/css';
import config from '../.tokenami/tokenami.config';
export const css = createCss(config);
```
#### 2. Configure the aliases in your config file:
```ts
export default createConfig({
aliases: {
p: ['padding'],
px: ['padding-inline'],
py: ['padding-block'],
size: ['width', 'height'],
},
});
```
#### 3. Use the aliases:
```tsx
```
### Theming properties
Tokenami maps your properties to some [default theme keys](https://github.com/tokenami/tokenami/blob/main/packages/tokenami/stubs/tokenami.config.ts#L43) 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:
```ts
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:
```tsx
```
### Custom properties
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:
```ts
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:
```tsx
```
## TypeScript integration
### Utility types
#### Variants
Use the `Variants` type to extend your component props with the available variants:
```tsx
import { type Variants } from '@tokenami/css';
type ButtonElementProps = React.ComponentPropsWithoutRef<'button'>;
interface ButtonProps extends ButtonElementProps, Variants {}
```
#### TokenamiStyle
For components using the `css` utility, use `TokenamiStyle` to type its style prop:
```tsx
import { type TokenamiStyle, css } from '@tokenami/css';
interface ButtonProps extends TokenamiStyle> {}
function Button(props: ButtonProps) {
return ;
}
```
Now you can pass Tokenami properties directly with proper type checking:
```tsx
```
### TokenValue
Use `TokenValue` to get a union of CSS variable tokens based on your theme.
Given this theme:
```ts
export default createConfig({
theme: {
color: {
'slate-100': '#f1f5f9',
'slate-700': '#334155',
},
radii: {
rounded: '10px',
circle: '9999px',
},
},
});
```
It will output the following types:
```ts
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)
```
### CI setup
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 custom Tokenami errors.
For accurate type checking in CI, run both commands:
```sh
tokenami check; tsc --noEmit
```
## Troubleshooting
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](https://discord.gg/CAU4HNR4XK).
### Why the CSS variable syntax?
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.
### VSCode setup
VSCode won't suggest completions for partial strings by default. This can make it harder to use Tokenami's suggestions. To fix this, add to your `.vscode/settings.json`:
```json
{
"editor.quickSuggestions": {
"strings": true
}
}
```
| BEFORE | AFTER |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|  |  |
### Supported libraries
Tokenami currently works with:
| 
React | 
Preact | 
Vue | 
SolidJS | 
Qwik |
| :-------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------: | :-------------------------------------------------------------------: | :-------------------------------------------------------------------------: | :----------------------------------------------------------------------: |
| ✅ | ✅ | ✅ | ✅ | ✅ |
We're still in early development and plan to support more libraries in the future.
### Supported browsers
Tokenami relies on [cascade layers](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/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+ |
### Supported editors
Tokenami is officially supported in the following editors:
- [VSCode](https://code.visualstudio.com/)
- [Cursor](https://www.cursor.com/)
- [Zed](https://zed.dev/)
### Browserslist
You can use [browserslist](https://browsersl.ist/) 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](https://github.com/tokenami/tokenami/issues/103).
> [!Important]
> Tokenami does not support browsers below the listed [supported browser versions](#user-content-browser-support). We recommend using `"browserslist": ["supports css-cascade-layers"]` if you're unsure.
## Community
### Contributing
Before raising a bug, please check if it's [already in our todo list](https://github.com/tokenami/tokenami/issues). Need help? Join our [Discord server](https://discord.gg/CAU4HNR4XK).
### Contributors
- Paweł Błaszczyk ([@pawelblaszczyk\_](https://twitter.com/pawelblaszczyk_))
### Credits
A big thanks to:
- [Tailwind V3](https://v3.tailwindcss.com/) for inspiring many of Tokenami's features
- [Stitches](https://stitches.dev/) for variants inspiration
- [CSS Hooks](https://css-hooks.com/) for custom selectors inspiration
- [Lightning CSS](https://lightningcss.dev/) for generating the Tokenami stylesheet
Please do check out these projects if Tokenami isn't quite what you're looking for.