Extending and overriding
Three ways to change a stylesheet without editing it. They differ in whether the change wins over the sheet's variants, and in whether it makes a new sheet or reaches an existing component.
| API | Produces | Against the sheet's variants | Applies |
|---|---|---|---|
sheet.extend(rules) | a new sheet | the variants still win | where the new sheet is used |
overrideSheet(sheet, rules) | a new sheet | the override wins | where the new sheet is used |
StyleOverrides | nothing new | the override wins | to that sheet, inside one subtree |
The examples change the button from Getting Started: its buttonStyles sheet has an accent background and a size variant that sets the padding.
New defaults with extend
extend merges rules into the sheet's base declarations and returns a new sheet with the same parts, variants and defaults. The variants still apply on top, so a value they set is not changed. It can also add parts.
import { buttonStyles } from './styles.ts'
export const linkButtonStyles = buttonStyles.extend({
// Every size keeps its padding; only the background changes.
Root: { background: 'neutral' },
// A part the original does not have.
Icon: { $kind: 'text', text: 'caption' },
})A layer above the variants with overrideSheet
overrideSheet returns a new sheet whose rules apply after the original's variants. A third argument adds variant rules of its own, over the same axes. null removes the declaration at that exact place.
import { overrideSheet } from '@toned/core'
import { buttonStyles } from './styles.ts'
export const compactButtonStyles = overrideSheet(
buttonStyles,
{
// Wins over the padding that the size variants set.
Root: { padding: 1 },
// Removes the base declaration. A variant that sets text still applies.
Label: { text: null },
},
$ => ({
[$.size('m')]: { Label: { text: 'caption' } },
}),
)A sheet made either way is an ordinary sheet. Bind it like the original:
import { createElements } from '@toned/react'
import { compactButtonStyles } from './compact-button.styles.ts'
const S = createElements(compactButtonStyles)
export function CompactButton({ label }: { label: string }) {
return (
<S>
<S.Root as="button" type="button">
<S.Label as="span">{label}</S.Label>
</S.Root>
</S>
)
}An existing component, inside one subtree
StyleOverrides changes a sheet for the components rendered inside it, without touching those components. overrideStyles pairs the sheet with the rules; its .variants() adds variant rules. Declare the list at module scope so it is the same array on every render.
import { overrideStyles, StyleOverrides } from '@toned/react'
import { Button } from './Button.tsx'
import { buttonStyles } from './styles.ts'
const toolbarOverrides = [
overrideStyles(buttonStyles, {
Root: { background: 'neutral' },
}).variants($ => ({
[$.size('s')]: { Root: { padding: 2 } },
})),
]
export function Toolbar() {
return (
<StyleOverrides value={toolbarOverrides}>
<Button label="Undo" size="s" />
<Button label="Redo" size="s" />
</StyleOverrides>
)
}An entry matches the exact sheet object it names. An override of buttonStyles does not reach a component that renders compactButtonStyles; name that sheet in its own entry. Nested StyleOverrides add to the outer ones, and the inner entry wins where both set a value.
Include derived sheets in the build
A sheet from extend or overrideSheet that adds a condition, a state or web rules needs CSS of its own. List it in the build's sheets with the others; the same holds for an overrideStyles entry, which is why these are module-scope values rather than something built during render.
import toned from '@toned/core/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { compactButtonStyles } from './compact-button.styles.ts'
import { linkButtonStyles } from './link-button.styles.ts'
import { buttonStyles } from './styles.ts'
import { ui } from './system.ts'
export default defineConfig({
plugins: [
toned({
system: ui,
sheets: [buttonStyles, linkButtonStyles, compactButtonStyles],
inputs: ['system.ts', 'styles.ts', 'link-button.styles.ts', 'compact-button.styles.ts'],
}),
react(),
],
})Choosing one
If the difference is a fixed set of choices, add a variant to the sheet instead: it is typed at the call site and needs none of the above. Use extend for a related component with different defaults, overrideSheet when a value must hold whatever variant is selected, and StyleOverrides when the component is not yours to change or the change belongs to one region of the application. The React reference specifies the precedence in full.