Skip to content

API reference

useStyles

useStyles is the React hook that connects a toned stylesheet to your component. It resolves tokens into platform-appropriate props (style, className, etc.) and applies variant matching based on the state you provide.

Signature

TypeScript
import { useStyles } from '@toned/react'
import { buttonStyles, cardStyles } from './styles.ts'

// Without variants
const card = useStyles(cardStyles)

// With variants
const button = useStyles(buttonStyles, { variant: 'accent', size: 'm' })

The examples on this page read their sheets from one module, shown at the end.

Parameters

stylesheet -- A stylesheet created with stylesheet(), optionally with .variants() chained. This is the style definition to resolve.

state -- An object of variant key-value pairs. Required when the stylesheet has required variants; omit when there are no variants. TypeScript enforces correctness here.

Return Value

An object with one key per element defined in the stylesheet. Each value is a props object that can be spread onto a JSX element:

TypeScript
const s = useStyles(buttonStyles, { size: 'm', variant: 'accent' })

// s.Root  => { style: {...}, className: '...' }
// s.Label => { style: {...}, className: '...' }

return (
  <button {...s.Root}>
    <span {...s.Label}>Click me</span>
  </button>
)

How It Works

Each render creates a private candidate using the current configuration, tokens, overrides and variants. The candidate becomes committed in a layout effect after host mutations. Suspended renders cannot publish pending inputs. Committed interaction and measurement updates can patch hosts directly without rendering React; immutable matching plans are reused.

Stable element families

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

const S = createElements(buttonStyles)

function Button() {
  return <S size="m" variant="accent">
    <S.Root as="button"><S.Label as="span">Save</S.Label></S.Root>
  </S>
}

The provider is hostless and carries the current render snapshot to its parts. Independent standalone parts use base/default styles; relationships and grid parts require shared scope. Existing useBind/$scope APIs remain compatible. Use prop bags when spreading onto a host is the better fit.

Usage Patterns

Static Styles (No Variants)

For stylesheets without variants, call useStyles with just the stylesheet:

Card.tsx
import { useStyles } from '@toned/react'
import { cardStyles } from './styles.ts'

function Card({ children }: { children: React.ReactNode }) {
  const s = useStyles(cardStyles)
  return <div {...s.Root}>{children}</div>
}

Dynamic Variants

For stylesheets with variants, pass the variant state as the second argument. The styles follow the state whenever it changes:

TypeScript
function NavLink({ href, label, isActive }: {
  href: string; label: string; isActive: boolean
}) {
  const s = useStyles(navStyles, {
    active: isActive,
  })
  return <a href={href} {...s.Link}>{label}</a>
}

Forwarding Props

Since useStyles returns plain props objects, you can combine them with additional props:

TypeScript
function Input({ error, ...rest }:
  React.ComponentProps<'input'> & { error: boolean }
) {
  const s = useStyles(inputStyles, { error })
  return <input {...s.Input.withProps<'input'>(rest)} />
}

Two Parts on One Element

.with() merges another part of the same sheet onto the element, and skips a falsy argument. Each part keeps its own styles there, so removing one leaves the other intact. For a state with a fixed set of values, a variant is the simpler declaration.

TypeScript
function Field({ invalid }: { invalid: boolean }) {
  const s = useStyles(fieldStyles)
  return <input {...s.Input.with(invalid && s.Invalid)} />
}

Spread a prop bag whole and last. It carries the ref that attaches the part, so a ref, style or className written before the spread is replaced by it; pass those through withProps, which merges them.

Styles used on this page

The components above import these sheets. They use the base system's tokens; see stylesheet and variants for the declarations themselves.

styles.ts
import type { Variants } from '@toned/core'
import { stylesheet } from '@toned/systems/base'

export const cardStyles = stylesheet({
  Root: { bgColor: 'elevated', borderRadius: 'large', padding: 2 },
})

export 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 } },
}))

export const navStyles = stylesheet({
  Link: { $kind: 'text', textColor: 'muted' },
}).variants(($: Variants<{ active: boolean }>) => ({
  [$.active(true)]: { Link: { textColor: 'action' } },
}))

export const fieldStyles = stylesheet({
  Input: { borderWidth: 'thin', borderColor: 'default', borderRadius: 'medium' },
  Invalid: { borderColor: 'status_error' },
})

export const inputStyles = stylesheet({
  Input: { borderWidth: 'thin', borderColor: 'default', borderRadius: 'medium' },
}).variants(($: Variants<{ error: boolean }>) => ({
  [$.error(true)]: { Input: { borderColor: 'status_error' } },
}))