Getting Started
Toned combines typed design tokens, named parts and variants. React element families keep component identities stable while each mounted instance owns its styling state. Web styles are generated before rendering.
Install
@toned/core holds the system, stylesheets and the CSS build; @toned/react binds them to React 18 or 19.
npm install @toned/core @toned/reactThree more packages are optional: @toned/systems (a ready-made token vocabulary), @toned/themes (theme values for that vocabulary) and, as development dependencies, @toned/eslint-plugin and @toned/compiler (lint rules, the language server and the design tooling). The changelog lists what each release contains.
Declare a stylesheet
A stylesheet names the parts of a component, gives each part token values, and describes how they change per variant. It imports stylesheet from the system module defined further down.
import type { Variants } from '@toned/core'
import { stylesheet } from './system.ts'
export const buttonStyles = stylesheet({
Root: { $kind: 'pressable', background: 'accent' },
Label: { $kind: 'text', text: 'label' },
}).variants(($: Variants<{ size: 's' | 'm' }>) => ({
[$.size('s')]: { Root: { padding: 1 }, Label: { text: 'caption' } },
[$.size('m')]: { Root: { padding: 2 } },
}), { defaults: { size: 'm' } })Render the parts
Bind the sheet once, at module scope. Variant values go on the family provider; host props go on the parts.
import { createElements } from '@toned/react'
import { buttonStyles } from './styles.ts'
const S = createElements(buttonStyles)
export function Button({ label, size = 'm' }: {
label: string
size?: 's' | 'm'
}) {
return (
<S size={size}>
<S.Root as="button" type="button">
<S.Label as="span">{label}</S.Label>
</S.Root>
</S>
)
}Define the system
The system holds the tokens the sheet used above. Keep it, like the sheets, in a pure module that both the build and the application import.
import { defineSystem, defineToken } from '@toned/core'
export const ui = defineSystem({
id: 'example',
tokens: {
background: defineToken({
values: ['neutral', 'accent'] as const,
resolve: value => ({ backgroundColor: value === 'accent' ? '#315bd6' : '#eee' }),
}),
padding: defineToken({
values: [1, 2, 3] as const,
resolve: step => ({ padding: step * 4 }),
}),
text: defineToken({
values: ['label', 'caption'] as const,
resolve: value => ({ color: '#fff', fontSize: value === 'label' ? 14 : 12 }),
}),
},
})
export const { stylesheet } = uiBuild every sheet
The Vite plugin emits static CSS and a matching manifest. Include lazy-route sheets in the inventory too; rendering does not discover missing CSS.
import toned from '@toned/core/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { buttonStyles } from './styles.ts'
import { ui } from './system.ts'
export default defineConfig({
plugins: [
toned({ system: ui, sheets: [buttonStyles], inputs: ['system.ts', 'styles.ts'] }),
react(),
],
})Without Vite, call buildStyles(ui, { sheets }) from @toned/core/build in your build script and serve its CSS and manifest together.
Configure and render
The application creates one renderer from the system and the generated manifest, and provides it to the tree.
import 'virtual:toned.css'
import manifest from 'virtual:toned.manifest'
import { createWebRenderer } from '@toned/core/server'
import { TonedProvider } from '@toned/react'
import { webHost } from '@toned/react/hosts/web'
import { Button } from './Button.tsx'
import { ui } from './system.ts'
const renderer = createWebRenderer(ui, { manifest })
export function App() {
return (
<TonedProvider renderer={renderer} host={webHost}>
<Button label="Save" size="s" />
</TonedProvider>
)
}/// <reference types="vite/client" />
declare module 'virtual:toned.css' {}
declare module 'virtual:toned.manifest' {
import type { BuildManifest } from '@toned/core/build'
const manifest: BuildManifest
export default manifest
}The family provider adds no DOM element. Independent parts can render standalone with base/default styles; use the provider for variant inputs, cross-part states, and grid ownership. Sibling providers remain isolated.
useStyles prop bags and the existing useBind API remain supported. See the React bindings guide for their contracts.