Skip to content

Guides

React Web Guide

This guide sets up Toned in a React web project with Vite and the ready-made base token vocabulary. With another bundler, generate the CSS in a build script instead; see Without Vite.

1. Install Dependencies

Terminal
npm install @toned/core @toned/react @toned/systems @toned/themes

2. Compose the System

@toned/systems/base exports its token vocabulary. Compose it into a system of your own, in a pure module that both the build and the application import. The base tokens read unprefixed custom properties, which @toned/themes supplies, so this system has no namespace id:

system.ts
import { defineSystem } from '@toned/core'
import { system as base } from '@toned/systems/base'

const { breakpoints, ...tokens } = base

export const ui = defineSystem(tokens, { breakpoints })
export const { stylesheet } = ui

A system of your own design follows Getting Started instead.

3. Define Styles

Sheets import stylesheet from the system module. Keep them separate from components, so the build can collect them:

styles/button.ts
import type { Variants } from '@toned/core'
import { stylesheet } from '../system.ts'

export const buttonStyles = stylesheet({
  Root: {
    $kind: 'pressable',
    bgColor: 'action',
    borderRadius: 'medium',
    borderWidth: 'none',
    paddingX: 3,
    paddingY: 2,
    cursor: 'pointer',
  },
  Label: {
    $kind: 'text',
    textColor: 'on_action',
    typography: 'label-medium',
  },
}).variants(($: Variants<{
  variant: 'primary' | 'secondary'
}>) => ({
  [$.variant('secondary')]: {
    Root: {
      bgColor: 'action_secondary',
    },
    Label: {
      textColor: 'on_action_secondary',
    },
  },
}))

4. Add the Vite Plugin

The Vite plugin generates the CSS and its manifest at build time. Pass the complete system and every sheet, including those of lazy routes, and list the declaration modules it should watch:

vite.config.ts
import toned from '@toned/core/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { buttonStyles } from './src/styles/button.ts'
import { ui } from './src/system.ts'

export default defineConfig({
  plugins: [
    toned({
      system: ui,
      sheets: [buttonStyles],
      inputs: ['src/system.ts', 'src/styles/button.ts'],
    }),
    react(),
  ],
})

5. Provide the Renderer

Import the theme values and the generated CSS in your entry point, create a renderer from the system and the generated manifest, and provide it with the web host:

main.tsx
import '@toned/themes/shadcn/config.css'
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 { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './App.tsx'
import { ui } from './system.ts'

const renderer = createWebRenderer(ui, { manifest })

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <TonedProvider renderer={renderer} host={webHost}>
      <App />
    </TonedProvider>
  </StrictMode>,
)
env.d.ts
/// <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
}

6. Use Styles in Components

Bind your stylesheet once, at module scope, with createElements. Variants go on the family provider; host props go on the parts:

Button.tsx
import { createElements } from '@toned/react'
import { buttonStyles } from './styles/button.ts'

const S = createElements(buttonStyles)

export function Button({ label, variant = 'primary' }: {
  label: string
  variant?: 'primary' | 'secondary'
}) {
  return (
    <S variant={variant}>
      <S.Root as="button" type="button">
        <S.Label as="span">{label}</S.Label>
      </S.Root>
    </S>
  )
}

What the Renderer Decides

The renderer owns the system, the output backend and the manifest check; the host owns refs, events and elements. Components choose no output mode. A web renderer emits class names for the generated CSS: breakpoints are CSS @media rules, and :hover, :focus and :active are CSS, with no JavaScript event listeners. See Interactive Styles.

List every sheet in the plugin's sheets: Toned never injects missing CSS at runtime, and the renderer diagnoses a condition absent from the manifest. The React reference covers themes, multiple systems and native hosts.