Skip to content

Get started

Core Concepts

Toned is built around four core ideas: design tokens, systems, stylesheets, and variants. Together they give you type-safe, cross-platform styling with a single authoring model.

Design Tokens

Tokens are the atomic building blocks of your design system. Each token maps a semantic name to one or more platform-specific values. For example, the bgColor token accepts values like 'action', 'muted', or 'elevated'. On the web these resolve to CSS custom properties; on React Native they resolve to colour values directly.

TypeScript
// Tokens are used directly inside stylesheet definitions:
Root: {
  bgColor: 'action',       // semantic background colour
  borderRadius: 'medium',  // semantic border radius
  paddingX: 3,             // spacing scale value
}

The base system ships with tokens for colour (bgColor, textColor, borderColor), borders (borderRadius, borderWidth), typography (typography), shadows (shadow), layout (paddingX, paddingY, gap, flexLayout), and sizing (width, height).

Systems

A system is a collection of tokens plus configuration (breakpoints, selectors, rules). You create one with defineSystem:

TypeScript
import { defineSystem, defineToken } from '@toned/core'

export const ui = defineSystem({
  id: 'example',
  tokens: {
    padding: defineToken({
      values: [0, 1, 2, 3] as const,
      resolve: value => ({ padding: value * 4 }),
    }),
  },
})

The system's ui.stylesheet function is bound to that system's token set, giving you full autocompletion and type checking for every token property.

Stylesheets

A stylesheet defines one or more named elements, each with a set of token values and an optional $style escape hatch for raw CSS properties:

TypeScript
const cardStyles = stylesheet({
  Root: {
    bgColor: 'elevated',
    borderRadius: 'large',
    borderColor: 'subtle',
    borderWidth: 'thin',
    shadow: 'small',
    padding: 3,
  },
})

Stylesheets are plain objects until they are consumed by a framework adapter (like useStyles in React). This keeps your style definitions platform-agnostic.

Variants

Variants let you conditionally apply different token values based on component state. Chain .variants() onto a stylesheet and use the dollar-sign builder to declare variant conditions:

TypeScript
import type { Variants } from '@toned/core'

const buttonStyles = stylesheet({
  Root: { $kind: 'pressable', bgColor: 'action', borderRadius: 'medium' },
  Label: { $kind: 'text', textColor: 'on_action' },
}).variants(($: Variants<{
  size: 'm' | 's'
  variant: 'accent' | 'danger'
}>) => ({
  [$.variant('danger')]: {
    Root: { bgColor: 'destructive' },
    Label: { textColor: 'on_destructive' },
  },
  [$.size('m')]: {
    Root: { paddingX: 3 },
  },
  [$.size('s')]: {
    Root: { paddingX: 2, paddingY: 1 },
  },
}))

Variant keys are fully typed: your component will get compile-time errors if it passes invalid variant values to the element family or to useStyles.

Media Queries / Breakpoints

The base system defines breakpoints (xs, sm, md, lg, xl). You can apply responsive token values under an @media key that names the breakpoint:

TypeScript
const responsiveCard = stylesheet({
  Root: {
    paddingX: 2,
    '@media md': {
      paddingX: 4,
    },
    '@media lg': {
      paddingX: 6,
    },
  },
})

The same @ blocks work inline with t, alongside ':hover' and friends.

With a web renderer these are real CSS @media rules, generated at build time. A native renderer evaluates a stylesheet's breakpoints against the host's viewport. Inline t blocks compile to CSS custom properties and apply to web output only. See Media Queries.

States and Conditions

Breakpoints are one kind of condition. A part can also change in a state (':hover', ':focus-visible'), inside a container of a given width ('@container card wide'), on one platform ('@platform web'), or when another part is in a state ('Root:hover'). Every key and its forms are listed in Conditions and selectors.