Skip to content

Themes

One interface, written once against a typed token vocabulary, and 6 themes that supply different values for it. Switching theme changes one attribute on the root element. It is CSS only: no component rerenders for it, and no class or inline style changes.

Soft neutrals, underlined tabs, rounded corners, light shadows.

Slipway

Web app

Stable

Frankfurt · 4 releases

Deploys this week
24
+6vs last week
Success rate
98.2%
+0.4points, 30 days
Median build
3m 12s
+18svs last week
VersionBranchStatusOwnerRollout
4.12.0mainRolling out
Ada King
60%
4.11.2hotfix/loginLive
Mo Rahimi
100%
4.11.1mainFailed
Jun Lee
20%
4.11.0mainLive
Ada King
100%

New release

It is added to the top of the table.
Start the rollout at

How it is built

Stylesheets name roles such as fill: 'accent' or edge: 'panel'. Each token resolves a role from the active theme, and a theme is an object that satisfies one type. On the web every theme field is a custom property, so the generated classes stay the same and only the values under data-theme differ.

1. A stylesheet

The button used throughout the demo. It is the same in every theme.

sheets/button.ts
import type { Variants } from '@toned/core'

import { stylesheet } from '../system.ts'

/**
 * A button, written once. Nothing here names a theme: every value is a role
 * (`fill: 'accent'`, `edge: 'filled'`, `press: 'down'`) that the active theme
 * resolves.
 */
export const buttonStyles = stylesheet({
  Root: {
    $kind: 'pressable',
    reset: 'control',
    flow: 'inline',
    align: 'center',
    justify: 'center',
    gap: 2,
    size: 'control',
    padX: 4,
    overflow: 'nowrap',
    type: 'control',
    fill: 'accent',
    ink: 'on-accent',
    edge: 'filled',
    corner: 'control',
    depth: 'button',
    motion: 'themed',
    // Text the theme draws around the label, e.g. `[ Save ]`.
    affix: 'button',
    ':hover': { fill: 'accent-hover' },
    ':focus-visible': { focus: 'ring' },
    ':active': { press: 'down' },
  },
}).variants(
  (
    $: Variants<{
      tone: 'primary' | 'secondary' | 'danger' | 'quiet'
      shape: 'label' | 'icon'
      disabled: boolean
    }>,
  ) => ({
    [$.tone('secondary')]: {
      Root: {
        fill: 'neutral',
        ink: 'on-neutral',
        edge: 'control',
        ':hover': { fill: 'neutral-hover' },
      },
    },
    [$.tone('danger')]: {
      Root: {
        fill: 'danger',
        ink: 'on-danger',
        ':hover': { fill: 'danger-hover' },
      },
    },
    [$.tone('quiet')]: {
      Root: {
        fill: 'none',
        ink: 'default',
        edge: 'none',
        depth: 'none',
        ':hover': { fill: 'selected', ink: 'on-selected' },
      },
    },
    [$.shape('icon')]: { Root: { size: 'square', padX: 0 } },
    [$.disabled(true)]: { Root: { state: 'disabled' } },
  }),
  { defaults: { tone: 'primary', shape: 'label', disabled: false } },
)
Button.tsx
import { createElements } from '@toned/react'
import { buttonStyles } from './sheets/button.ts'

const ButtonParts = createElements(buttonStyles)

export function Button({
  tone,
  shape,
  ...props
}: ButtonHTMLAttributes<HTMLButtonElement> & {
  tone?: 'primary' | 'secondary' | 'danger' | 'quiet'
  shape?: 'label' | 'icon'
}) {
  return (
    <ButtonParts tone={tone} shape={shape} disabled={props.disabled ?? false}>
      <ButtonParts.Root as="button" type="button" {...props} />
    </ButtonParts>
  )
}

2. The themes

Modern is the default theme. It supplies 118 values.

FieldModern
titleBarnone
statusBarnone
buttonOpennone
buttonClosenone
bulletnone
cursornone
blurnone
sheenlinear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0))
displayStylenormal
displayTracking-0.02em
smoothingantialiased
iconRenderingauto
radiusAppBar0
radiusTabTray0
barInset0
tabTrayPad0
tabTrayAlignstretch
shadowAppBarnone
shadowSelectednone
disabledOpacity0.45
stagelinear-gradient(180deg, #f1f3f7 0%, #e6e9f0 100%)
surface#f7f8fa
raised#ffffff
sunken#ffffff
overlay#ffffff
ink#1f2633
inkStrong#0d1220
inkMuted#5a6375
accent#4f46e5
accentHover#4338ca
onAccent#ffffff
neutral#ffffff
neutralHover#f3f4f7
onNeutral#1f2633
danger#c9271b
dangerHover#ab2016
onDanger#ffffff
selected#eceefe
onSelected#3730a3
hover#f3f4f7
bar#ffffff
onBar#1f2633
head#fafbfc
onHead#2a3140
ok#dcfce7
onOk#166534
warn#fef3c7
onWarn#92400e
bad#fee2e2
onBad#991b1b
line#e4e7ec
lineStrong#d0d5dd
lineFilledtransparent
rule#eaecf0
cutout#f7f8fa
track#e4e7ec
meter#4f46e5
tray#eef0f4
thumb#ffffff
onThumb#0d1220
knob#ffffff
tabTraytransparent
tabSelectedtransparent
onTabSelected#0d1220
tabIndicator#4f46e5
chart#c7cbf9
caret#4f46e5
scrimrgba(15, 20, 35, 0.45)
fontBodysystem-ui, -apple-system, "Segoe UI", Roboto, sans-serif
fontDisplaysystem-ui, -apple-system, "Segoe UI", Roboto, sans-serif
fontDatasystem-ui, -apple-system, "Segoe UI", Roboto, sans-serif
sizeBody14px
sizeSmall12px
sizeTitle15px
sizeDisplay26px
weightBody400
weightStrong600
weightDisplay700
leading1.5
capsnone
labelCasenone
labelTracking0
radiusControl8px
radiusInner6px
radiusTab0
radiusPanel12px
radiusWindow14px
radiusBadge999px
radiusPill999px
radiusChart3px
panelRule1px
panelBorder1px
panelBorderStylesolid
controlBorder1px
fieldRule1px
fieldBorder1px
controlBorderStylesolid
ruleWidth1px
ruleStylesolid
barRule1px
tabRule2px
meterHeight6px
focusRing2px solid #4f46e5
focusOffset2px
shadowPanel0 1px 2px rgba(16, 24, 40, 0.05)
shadowControl0 1px 2px rgba(16, 24, 40, 0.05)
shadowButton0 1px 2px rgba(16, 24, 40, 0.08)
shadowField0 1px 2px rgba(16, 24, 40, 0.04)
shadowThumb0 1px 2px rgba(16, 24, 40, 0.14), 0 0 0 1px rgba(16, 24, 40, 0.04)
shadowKnob0 1px 3px rgba(16, 24, 40, 0.24)
shadowTabTrayinset 0 -1px 0 0 #e4e7ec
shadowOverlay0 0 0 1px rgba(16, 24, 40, 0.04), 0 4px 8px -2px rgba(16, 24, 40, 0.06), 0 16px 40px -6px rgba(16, 24, 40, 0.2)
shadowWindow0 1px 2px rgba(16, 24, 40, 0.06), 0 24px 48px -12px rgba(16, 24, 40, 0.16)
unit4px
controlHeight36px
duration150ms
easingease
pressscale(0.98)
themes.ts
import type { Theme } from './theme.ts'

/**
 * Six themes for one set of stylesheets. A theme is a plain object of values:
 * it holds no selectors and names no component.
 */

const sans = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
const mono =
  'ui-monospace, "SF Mono", Menlo, Consolas, "Courier New", monospace'

/** What most themes leave off, or leave at the browser's default. */
const plain = {
  titleBar: 'none',
  statusBar: 'none',
  buttonOpen: 'none',
  buttonClose: 'none',
  bullet: 'none',
  cursor: 'none',
  blur: 'none',
  sheen: 'none',
  displayStyle: 'normal',
  displayTracking: '0',
  smoothing: 'antialiased',
  iconRendering: 'auto',
  radiusAppBar: '0',
  radiusTabTray: '0',
  barInset: '0',
  tabTrayPad: '0',
  tabTrayAlign: 'stretch',
  shadowAppBar: 'none',
  shadowSelected: 'none',
  disabledOpacity: '0.45',
} as const

const modern: Theme = {
  ...plain,
  stage: 'linear-gradient(180deg, #f1f3f7 0%, #e6e9f0 100%)',
  surface: '#f7f8fa',
  raised: '#ffffff',
  sunken: '#ffffff',
  overlay: '#ffffff',
  ink: '#1f2633',
  inkStrong: '#0d1220',
  inkMuted: '#5a6375',
  accent: '#4f46e5',
  accentHover: '#4338ca',
  onAccent: '#ffffff',
  neutral: '#ffffff',
  neutralHover: '#f3f4f7',
  onNeutral: '#1f2633',
  danger: '#c9271b',
  dangerHover: '#ab2016',
  onDanger: '#ffffff',
  sheen:
    'linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0))',
  selected: '#eceefe',
  onSelected: '#3730a3',
  hover: '#f3f4f7',
  bar: '#ffffff',
  onBar: '#1f2633',
  head: '#fafbfc',
  onHead: '#2a3140',
  ok: '#dcfce7',
  onOk: '#166534',
  warn: '#fef3c7',
  onWarn: '#92400e',
  bad: '#fee2e2',
  onBad: '#991b1b',
  line: '#e4e7ec',
  lineStrong: '#d0d5dd',
  lineFilled: 'transparent',
  rule: '#eaecf0',
  cutout: '#f7f8fa',
  track: '#e4e7ec',
  meter: '#4f46e5',
  tray: '#eef0f4',
  thumb: '#ffffff',
  onThumb: '#0d1220',
  knob: '#ffffff',
  tabTray: 'transparent',
  tabSelected: 'transparent',
  onTabSelected: '#0d1220',
  tabIndicator: '#4f46e5',
  chart: '#c7cbf9',
  caret: '#4f46e5',
  scrim: 'rgba(15, 20, 35, 0.45)',

  fontBody: sans,
  fontDisplay: sans,
  fontData: sans,
  sizeBody: '14px',
  sizeSmall: '12px',
  sizeTitle: '15px',
  sizeDisplay: '26px',
  weightBody: '400',
  weightStrong: '600',
  weightDisplay: '700',
  leading: '1.5',
  caps: 'none',
  labelCase: 'none',
  labelTracking: '0',
  displayTracking: '-0.02em',

  radiusControl: '8px',
  radiusInner: '6px',
  radiusTab: '0',
  radiusPanel: '12px',
  radiusWindow: '14px',
  radiusBadge: '999px',
  radiusPill: '999px',
  radiusChart: '3px',
  panelRule: '1px',
  panelBorder: '1px',
  panelBorderStyle: 'solid',
  controlBorder: '1px',
  fieldRule: '1px',
  fieldBorder: '1px',
  controlBorderStyle: 'solid',
  ruleWidth: '1px',
  ruleStyle: 'solid',
  barRule: '1px',
  tabRule: '2px',
  meterHeight: '6px',
  focusRing: '2px solid #4f46e5',
  focusOffset: '2px',

  shadowPanel: '0 1px 2px rgba(16, 24, 40, 0.05)',
  shadowControl: '0 1px 2px rgba(16, 24, 40, 0.05)',
  shadowButton: '0 1px 2px rgba(16, 24, 40, 0.08)',
  shadowField: '0 1px 2px rgba(16, 24, 40, 0.04)',
  shadowThumb:
    '0 1px 2px rgba(16, 24, 40, 0.14), 0 0 0 1px rgba(16, 24, 40, 0.04)',
  shadowKnob: '0 1px 3px rgba(16, 24, 40, 0.24)',
  // Tabs are underlined: the rule under the list is this inset shadow.
  shadowTabTray: 'inset 0 -1px 0 0 #e4e7ec',
  shadowOverlay:
    '0 0 0 1px rgba(16, 24, 40, 0.04), 0 4px 8px -2px rgba(16, 24, 40, 0.06), 0 16px 40px -6px rgba(16, 24, 40, 0.2)',
  shadowWindow:
    '0 1px 2px rgba(16, 24, 40, 0.06), 0 24px 48px -12px rgba(16, 24, 40, 0.16)',

  unit: '4px',
  controlHeight: '36px',
  duration: '150ms',
  easing: 'ease',
  press: 'scale(0.98)',
}

/** A stepped outline drawn with four offset shadows, so corners stay notched. */
const pixels = (color: string) =>
  `0 -4px 0 0 ${color}, 0 4px 0 0 ${color}, -4px 0 0 0 ${color}, 4px 0 0 0 ${color}`

const pixelFont = `Monaco, "Lucida Console", ${mono}`
/** Dark outline, lit top-left edge, shaded bottom-right edge. */
const pixelBevel = `${pixels('#0d0e17')}, inset -4px -4px 0 0 rgba(0, 0, 0, 0.25), inset 4px 4px 0 0 rgba(255, 255, 255, 0.35)`

// Colours are the 16 of the Sweetie 16 palette, plus a darker shade for
// shadows. Every length is a multiple of the 4px pixel.
const pixel: Theme = {
  ...plain,
  stage: 'conic-gradient(#29366f 25%, transparent 0) 0 0 / 16px 16px, #1a1c2c',
  surface: '#333c57',
  raised: '#1a1c2c',
  sunken: '#1a1c2c',
  overlay: '#29366f',
  ink: '#f4f4f4',
  inkStrong: '#ffcd75',
  inkMuted: '#a9c0cf',
  accent: '#ffcd75',
  accentHover: '#a7f070',
  onAccent: '#1a1c2c',
  neutral: '#94b0c2',
  neutralHover: '#f4f4f4',
  onNeutral: '#1a1c2c',
  danger: '#b13e53',
  dangerHover: '#ef7d57',
  onDanger: '#f4f4f4',
  selected: '#3b5dc9',
  onSelected: '#f4f4f4',
  hover: '#29366f',
  bar: '#1a1c2c',
  onBar: '#f4f4f4',
  head: '#3b5dc9',
  onHead: '#f4f4f4',
  ok: '#38b764',
  onOk: '#1a1c2c',
  warn: '#ffcd75',
  onWarn: '#1a1c2c',
  bad: '#b13e53',
  onBad: '#f4f4f4',
  line: '#f4f4f4',
  lineStrong: '#f4f4f4',
  lineFilled: 'transparent',
  rule: '#566c86',
  cutout: '#333c57',
  track: '#566c86',
  meter:
    'repeating-linear-gradient(90deg, #a7f070 0 12px, transparent 12px 16px)',
  tray: '#1a1c2c',
  thumb: '#3b5dc9',
  onThumb: '#f4f4f4',
  knob: '#f4f4f4',
  tabTray: 'transparent',
  tabSelected: '#3b5dc9',
  onTabSelected: '#f4f4f4',
  tabIndicator: 'transparent',
  chart: '#3b5dc9',
  caret: '#ffcd75',
  scrim: 'rgba(26, 28, 44, 0.82)',

  fontBody: pixelFont,
  fontDisplay: pixelFont,
  fontData: pixelFont,
  sizeBody: '13px',
  sizeSmall: '11px',
  sizeTitle: '16px',
  sizeDisplay: '24px',
  weightBody: '700',
  weightStrong: '700',
  weightDisplay: '700',
  leading: '1.5',
  caps: 'uppercase',
  labelCase: 'uppercase',
  labelTracking: '0.06em',
  smoothing: 'none',
  iconRendering: 'crispEdges',

  radiusControl: '0',
  radiusInner: '0',
  radiusTab: '0',
  radiusPanel: '0',
  radiusWindow: '0',
  radiusBadge: '0',
  radiusPill: '0',
  radiusChart: '0',
  // The outline is the stepped shadow below, not a border.
  panelRule: '0',
  panelBorder: '0',
  panelBorderStyle: 'solid',
  controlBorder: '0',
  fieldRule: '4px',
  fieldBorder: '4px',
  controlBorderStyle: 'solid',
  ruleWidth: '4px',
  ruleStyle: 'solid',
  barRule: '4px',
  tabRule: '0',
  meterHeight: '8px',
  focusRing: '4px solid #73eff7',
  focusOffset: '4px',

  shadowPanel: `${pixels('#f4f4f4')}, 4px 8px 0 0 #0d0e17, 8px 4px 0 0 #0d0e17`,
  shadowControl: pixelBevel,
  shadowButton: pixelBevel,
  shadowField: 'none',
  shadowThumb: 'none',
  shadowKnob: `${pixels('#0d0e17')}, inset -4px -4px 0 0 rgba(0, 0, 0, 0.25)`,
  shadowTabTray: 'inset 0 -4px 0 0 #566c86',
  shadowOverlay: `${pixels('#f4f4f4')}, 8px 12px 0 0 #0d0e17, 12px 8px 0 0 #0d0e17`,
  shadowWindow: `${pixels('#f4f4f4')}, 8px 12px 0 0 #0d0e17, 12px 8px 0 0 #0d0e17`,

  unit: '4px',
  controlHeight: '40px',
  duration: '160ms',
  easing: 'steps(2, end)',
  press: 'translate(4px, 4px)',
  disabledOpacity: '0.5',
  bullet: '"> "',
}

// Glass is two layers. The window is the content layer: frosted, mostly
// opaque, so text keeps its contrast. Bars, wells, thumbs, buttons and
// overlays float above it: each catches light on its top edge, casts a soft
// shadow and is rounded concentrically with the layer that holds it
// (window 28 → app bar 18 and panel 20 → control 12 → inner 9).
//
// No part has a border. A border sits outside the padding box, so a gradient
// fill repeats under it and a translucent one doubles up there. Each edge is
// an inset hairline instead, with a second, brighter one along the top.
const glassShade = (alpha: number) => `rgba(18, 38, 84, ${alpha})`
const glassLight = (alpha: number) => `rgba(255, 255, 255, ${alpha})`
/** A hairline of light inside the edge, brighter along the top. */
const glassEdge = (around: number, top: number) =>
  `inset 0 0 0 1px ${glassLight(around)}, inset 0 1px 0 ${glassLight(top)}`
/** The hairline of a recessed part. */
const glassWell = `inset 0 0 0 1px ${glassShade(0.08)}`
const glassFont = (cut: string) =>
  `-apple-system, BlinkMacSystemFont, "SF Pro ${cut}", "Helvetica Neue", system-ui, sans-serif`

const glass: Theme = {
  ...plain,
  // One hue with a single warm glow, so the frosted window has light and
  // shade to pick up without turning the page into a rainbow.
  stage:
    'radial-gradient(42% 46% at 14% 20%, #cfe6ff 0, rgba(207, 230, 255, 0) 70%), radial-gradient(40% 52% at 86% 16%, #4f7df0 0, rgba(79, 125, 240, 0) 72%), radial-gradient(48% 54% at 74% 96%, #ffc9a6 0, rgba(255, 201, 166, 0) 70%), radial-gradient(42% 50% at 6% 92%, #2c63cf 0, rgba(44, 99, 207, 0) 72%), linear-gradient(160deg, #86b4f4 0%, #5683e2 55%, #8d94ea 100%)',
  surface: `linear-gradient(180deg, ${glassLight(0.76)} 0%, ${glassLight(0.6)} 100%)`,
  raised: glassLight(0.5),
  sunken: glassLight(0.58),
  // Nearly opaque: an overlay sits over text, and must stay legible where
  // the browser cannot blur what is behind it.
  overlay: `linear-gradient(180deg, ${glassLight(0.97)} 0%, rgba(244, 248, 255, 0.95) 100%)`,
  ink: '#0e1a33',
  inkStrong: '#06102a',
  inkMuted: 'rgba(14, 26, 51, 0.74)',
  accent: '#0453c4',
  accentHover: '#085cd2',
  onAccent: '#ffffff',
  neutral: `linear-gradient(180deg, ${glassLight(0.94)} 0%, ${glassLight(0.74)} 100%)`,
  neutralHover: glassLight(0.96),
  onNeutral: '#0e1a33',
  danger: '#c21d37',
  dangerHover: '#cc2540',
  onDanger: '#ffffff',
  // Light falling off from the top of a tinted control, shade along its foot.
  sheen:
    'linear-gradient(180deg, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 62%, rgba(0, 8, 40, 0.06) 100%)',
  selected: glassLight(0.74),
  onSelected: '#0a3f96',
  hover: glassLight(0.4),
  bar: glassLight(0.46),
  onBar: '#0e1a33',
  head: glassLight(0.3),
  onHead: '#0e1a33',
  ok: 'rgba(36, 178, 92, 0.2)',
  onOk: '#0a5226',
  warn: 'rgba(240, 160, 0, 0.24)',
  onWarn: '#6a4200',
  bad: 'rgba(235, 60, 70, 0.18)',
  onBad: '#8a1422',
  line: glassLight(0.5),
  lineStrong: glassLight(0.5),
  lineFilled: 'transparent',
  rule: 'rgba(14, 26, 51, 0.1)',
  cutout: glassLight(0.92),
  track: 'rgba(14, 26, 51, 0.13)',
  meter: 'linear-gradient(90deg, #3f8cff 0%, #0453c4 100%)',
  tray: 'rgba(14, 26, 51, 0.07)',
  thumb: glassLight(0.94),
  onThumb: '#06102a',
  knob: '#ffffff',
  tabTray: 'rgba(14, 26, 51, 0.07)',
  tabSelected: glassLight(0.94),
  onTabSelected: '#06102a',
  tabIndicator: 'transparent',
  chart: 'rgba(5, 88, 204, 0.3)',
  caret: '#0453c4',
  scrim: 'rgba(12, 26, 60, 0.3)',

  fontBody: glassFont('Text'),
  fontDisplay: glassFont('Display'),
  fontData: glassFont('Text'),
  sizeBody: '15px',
  sizeSmall: '13px',
  sizeTitle: '17px',
  sizeDisplay: '30px',
  weightBody: '400',
  weightStrong: '600',
  weightDisplay: '700',
  leading: '1.45',
  caps: 'none',
  labelCase: 'none',
  labelTracking: '0',
  displayTracking: '-0.022em',

  radiusControl: '12px',
  radiusInner: '9px',
  radiusTab: '9px',
  radiusTabTray: '13px',
  radiusPanel: '20px',
  radiusWindow: '28px',
  radiusAppBar: '18px',
  radiusBadge: '999px',
  radiusPill: '999px',
  radiusChart: '4px',
  // Edges are the inset hairlines in the shadows below, not borders.
  panelRule: '0',
  panelBorder: '0',
  panelBorderStyle: 'solid',
  controlBorder: '0',
  fieldRule: '0',
  fieldBorder: '0',
  controlBorderStyle: 'solid',
  ruleWidth: '1px',
  ruleStyle: 'solid',
  barRule: '0',
  tabRule: '0',
  meterHeight: '6px',
  focusRing: '3px solid #0453c4',
  focusOffset: '2px',

  shadowPanel: `${glassEdge(0.5, 0.9)}, 0 1px 2px ${glassShade(0.05)}, 0 8px 24px -12px ${glassShade(0.18)}`,
  // Wells are recessed: a dark hairline, shade under the top lip.
  shadowControl: `${glassWell}, inset 0 1px 2px ${glassShade(0.12)}`,
  // One shadow for every tone: the lit edge reads on a tinted button, the
  // cast shadow outlines a white one.
  shadowButton: `${glassEdge(0.2, 0.5)}, 0 1px 2px ${glassShade(0.2)}, 0 6px 14px -6px ${glassShade(0.3)}`,
  shadowField: `${glassWell}, inset 0 1px 2px ${glassShade(0.08)}`,
  shadowThumb: `${glassEdge(0.6, 1)}, 0 0 0 0.5px ${glassShade(0.1)}, 0 1px 2px ${glassShade(0.14)}, 0 3px 8px -3px ${glassShade(0.24)}`,
  shadowKnob: `0 0 0 0.5px ${glassShade(0.16)}, 0 1px 2px ${glassShade(0.2)}, 0 4px 10px -2px ${glassShade(0.28)}`,
  // Inset only: the lists that hold a selected item scroll, and would clip
  // a shadow cast outside it.
  shadowSelected: `${glassEdge(0.6, 1)}, inset 0 -1px 0 ${glassShade(0.06)}`,
  shadowAppBar: `${glassEdge(0.6, 1)}, 0 1px 2px ${glassShade(0.06)}, 0 10px 26px -12px ${glassShade(0.3)}`,
  shadowTabTray: `${glassWell}, inset 0 1px 2px ${glassShade(0.08)}`,
  shadowOverlay: `${glassEdge(0.7, 1)}, 0 0 0 0.5px ${glassShade(0.12)}, 0 2px 6px ${glassShade(0.08)}, 0 28px 64px -16px rgba(12, 30, 80, 0.45)`,
  shadowWindow: `${glassEdge(0.45, 0.95)}, 0 2px 4px rgba(12, 30, 80, 0.08), 0 36px 80px -24px rgba(12, 30, 80, 0.5)`,
  blur: 'blur(28px) saturate(170%)',

  unit: '5px',
  controlHeight: '40px',
  barInset: '10px',
  tabTrayPad: '4px',
  tabTrayAlign: 'flex-start',
  // Overshoots, then settles: a control springs back when released.
  duration: '320ms',
  easing: 'cubic-bezier(0.3, 1.4, 0.5, 1)',
  press: 'scale(0.96)',
}

// Text mode: one size on a character grid, the 16 CGA colours, double-line
// frames, a block shadow one cell behind each window, and no motion.
const dos: Theme = {
  ...plain,
  stage: 'repeating-conic-gradient(#0000aa 0 25%, #000055 0 50%) 0 0 / 4px 4px',
  surface: '#0000aa',
  raised: '#0000aa',
  sunken: '#000000',
  overlay: '#0000aa',
  ink: '#aaaaaa',
  inkStrong: '#ffffff',
  inkMuted: '#55ffff',
  accent: '#ffff55',
  accentHover: '#ffffff',
  onAccent: '#000000',
  neutral: '#aaaaaa',
  neutralHover: '#ffffff',
  onNeutral: '#000000',
  danger: '#aa0000',
  dangerHover: '#ff5555',
  onDanger: '#ffffff',
  selected: '#00aaaa',
  onSelected: '#000000',
  hover: '#000055',
  bar: '#aaaaaa',
  onBar: '#000000',
  head: 'transparent',
  onHead: '#ffff55',
  ok: '#00aa00',
  onOk: '#000000',
  warn: '#aa5500',
  onWarn: '#ffff55',
  bad: '#aa0000',
  onBad: '#ffffff',
  line: '#aaaaaa',
  lineStrong: '#aaaaaa',
  lineFilled: 'transparent',
  rule: '#aaaaaa',
  cutout: '#0000aa',
  // Two-pixel dither: the shaded block character of a text-mode gauge.
  track: 'repeating-conic-gradient(#aaaaaa 0 25%, #0000aa 0 50%) 0 0 / 2px 2px',
  meter: '#ffff55',
  tray: '#000000',
  thumb: '#00aaaa',
  onThumb: '#000000',
  knob: '#ffffff',
  tabTray: 'transparent',
  tabSelected: '#00aaaa',
  onTabSelected: '#000000',
  tabIndicator: 'transparent',
  chart: '#00aaaa',
  caret: '#ffff55',
  scrim: 'rgba(0, 0, 0, 0.6)',

  fontBody: mono,
  fontDisplay: mono,
  fontData: mono,
  // Text mode has one size; emphasis comes from colour.
  sizeBody: '16px',
  sizeSmall: '16px',
  sizeTitle: '16px',
  sizeDisplay: '16px',
  weightBody: '400',
  weightStrong: '400',
  weightDisplay: '400',
  leading: '1.25',
  caps: 'none',
  labelCase: 'none',
  labelTracking: '0',
  smoothing: 'none',
  iconRendering: 'crispEdges',

  radiusControl: '0',
  radiusInner: '0',
  radiusTab: '0',
  radiusPanel: '0',
  radiusWindow: '0',
  radiusBadge: '0',
  radiusPill: '0',
  radiusChart: '0',
  panelRule: '3px',
  panelBorder: '3px',
  panelBorderStyle: 'double',
  controlBorder: '0',
  fieldRule: '0',
  fieldBorder: '0',
  controlBorderStyle: 'solid',
  ruleWidth: '1px',
  ruleStyle: 'dashed',
  barRule: '0',
  tabRule: '0',
  meterHeight: '10px',
  focusRing: '2px solid #ffff55',
  focusOffset: '1px',

  shadowPanel: 'none',
  shadowControl: 'none',
  shadowButton: '8px 6px 0 0 #000000',
  shadowField: 'none',
  shadowThumb: 'none',
  shadowKnob: 'none',
  shadowTabTray: 'inset 0 -1px 0 0 #aaaaaa',
  shadowOverlay: '16px 14px 0 0 #000000',
  shadowWindow: '16px 14px 0 0 rgba(0, 0, 0, 0.6)',

  unit: '0.5ch',
  controlHeight: '28px',
  duration: '0s',
  easing: 'linear',
  press: 'translate(2px, 2px)',
  disabledOpacity: '0.5',
  titleBar: 'flex',
  statusBar: 'flex',
  buttonOpen: '"[ "',
  buttonClose: '" ]"',
  bullet: '"\\25BA  "',
  cursor: '"\\2588"',
}

const paperInk = '#1f1b16'
const paperSerif =
  '"Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif'

// A printed page: ink on cream, serif type, rules instead of boxes, and
// labels set in spaced capitals.
const paper: Theme = {
  ...plain,
  stage:
    'repeating-linear-gradient(transparent 0 31px, rgba(31, 27, 22, 0.06) 31px 32px), #e8dfc8',
  surface: '#fbf7ec',
  raised: '#fbf7ec',
  sunken: '#f3ecda',
  overlay: '#fffdf6',
  ink: paperInk,
  inkStrong: '#000000',
  inkMuted: '#5f574a',
  accent: paperInk,
  accentHover: '#3d362c',
  onAccent: '#fbf7ec',
  neutral: '#fbf7ec',
  neutralHover: '#efe6cc',
  onNeutral: paperInk,
  danger: '#8f2a1d',
  dangerHover: '#6f1f15',
  onDanger: '#fbf7ec',
  selected: '#efe6cc',
  onSelected: paperInk,
  hover: '#f3ecda',
  bar: '#fbf7ec',
  onBar: paperInk,
  head: 'transparent',
  onHead: paperInk,
  ok: '#e0ead2',
  onOk: '#2a5a2d',
  warn: '#f4e3b5',
  onWarn: '#6b4a00',
  bad: '#f3d6cc',
  onBad: '#7c1f14',
  line: paperInk,
  lineStrong: paperInk,
  lineFilled: paperInk,
  rule: 'rgba(31, 27, 22, 0.55)',
  cutout: '#fbf7ec',
  track: '#e3dac2',
  meter: paperInk,
  tray: 'transparent',
  thumb: paperInk,
  onThumb: '#fbf7ec',
  knob: '#fbf7ec',
  tabTray: 'transparent',
  tabSelected: 'transparent',
  onTabSelected: '#000000',
  tabIndicator: paperInk,
  chart: '#c9bea3',
  caret: '#8f2a1d',
  scrim: 'rgba(31, 27, 22, 0.45)',

  fontBody: paperSerif,
  fontDisplay:
    'Didot, "Bodoni 72", "Bodoni MT", "Playfair Display", Georgia, serif',
  fontData: paperSerif,
  sizeBody: '16px',
  sizeSmall: '12px',
  sizeTitle: '21px',
  sizeDisplay: '40px',
  weightBody: '400',
  weightStrong: '700',
  weightDisplay: '400',
  leading: '1.5',
  caps: 'none',
  labelCase: 'uppercase',
  labelTracking: '0.1em',
  displayStyle: 'italic',

  radiusControl: '0',
  radiusInner: '0',
  radiusTab: '0',
  radiusPanel: '0',
  radiusWindow: '0',
  radiusBadge: '2px',
  radiusPill: '999px',
  radiusChart: '0',
  // Ruled on one edge: a double line above a panel, a single line under a field.
  panelRule: '3px',
  panelBorder: '0',
  panelBorderStyle: 'double',
  controlBorder: '1px',
  fieldRule: '1px',
  fieldBorder: '0',
  controlBorderStyle: 'solid',
  ruleWidth: '1px',
  ruleStyle: 'dotted',
  barRule: '1px',
  tabRule: '2px',
  meterHeight: '4px',
  focusRing: '2px solid #8f2a1d',
  focusOffset: '3px',

  shadowPanel: 'none',
  shadowControl: 'none',
  shadowButton: 'none',
  shadowField: 'none',
  shadowThumb: 'none',
  shadowKnob: 'none',
  shadowTabTray: `inset 0 -1px 0 0 ${paperInk}`,
  shadowOverlay: '0 18px 50px rgba(60, 40, 10, 0.35)',
  shadowWindow:
    '0 1px 2px rgba(60, 40, 10, 0.25), 0 14px 34px rgba(60, 40, 10, 0.18)',

  unit: '5px',
  controlHeight: '36px',
  duration: '120ms',
  easing: 'ease',
  press: 'none',
}

// One border weight (3px) and two hard offsets: 4px for controls, 8px for
// everything that holds them.
const brutalist: Theme = {
  ...plain,
  stage: '#ffe600',
  surface: '#ffffff',
  raised: '#ffffff',
  sunken: '#ffffff',
  overlay: '#ffffff',
  ink: '#000000',
  inkStrong: '#000000',
  inkMuted: '#3d3d3d',
  accent: '#0038ff',
  accentHover: '#0026b3',
  onAccent: '#ffffff',
  neutral: '#ffffff',
  neutralHover: '#ffe600',
  onNeutral: '#000000',
  danger: '#ff3b30',
  dangerHover: '#ff7a72',
  onDanger: '#000000',
  selected: '#000000',
  onSelected: '#ffe600',
  hover: '#ffe600',
  bar: '#000000',
  onBar: '#ffffff',
  head: '#000000',
  onHead: '#ffffff',
  ok: '#00d26a',
  onOk: '#000000',
  warn: '#ffe600',
  onWarn: '#000000',
  bad: '#ff3b30',
  onBad: '#000000',
  line: '#000000',
  lineStrong: '#000000',
  lineFilled: '#000000',
  rule: '#000000',
  cutout: '#ffffff',
  track: '#d4d4d4',
  meter: '#0038ff',
  tray: '#ffffff',
  thumb: '#000000',
  onThumb: '#ffe600',
  knob: '#ffffff',
  tabTray: 'transparent',
  tabSelected: '#000000',
  onTabSelected: '#ffe600',
  tabIndicator: 'transparent',
  chart: '#000000',
  caret: '#0038ff',
  scrim: 'rgba(255, 230, 0, 0.72)',

  fontBody: '"Helvetica Neue", Helvetica, Arial, sans-serif',
  fontDisplay: '"Arial Black", "Helvetica Neue", Helvetica, Arial, sans-serif',
  fontData: '"Helvetica Neue", Helvetica, Arial, sans-serif',
  sizeBody: '15px',
  sizeSmall: '12px',
  sizeTitle: '18px',
  sizeDisplay: '32px',
  weightBody: '500',
  weightStrong: '800',
  weightDisplay: '900',
  leading: '1.35',
  caps: 'uppercase',
  labelCase: 'uppercase',
  labelTracking: '0.04em',
  displayTracking: '-0.02em',

  radiusControl: '0',
  radiusInner: '0',
  radiusTab: '0',
  radiusPanel: '0',
  radiusWindow: '0',
  radiusBadge: '0',
  radiusPill: '0',
  radiusChart: '0',
  panelRule: '3px',
  panelBorder: '3px',
  panelBorderStyle: 'solid',
  controlBorder: '3px',
  fieldRule: '3px',
  fieldBorder: '3px',
  controlBorderStyle: 'solid',
  ruleWidth: '3px',
  ruleStyle: 'solid',
  barRule: '3px',
  tabRule: '0',
  meterHeight: '12px',
  focusRing: '4px solid #ff3b30',
  focusOffset: '3px',

  shadowPanel: '8px 8px 0 0 #000000',
  shadowControl: 'none',
  shadowButton: '4px 4px 0 0 #000000',
  shadowField: 'none',
  shadowThumb: 'none',
  shadowKnob: 'none',
  shadowTabTray: 'inset 0 -3px 0 0 #000000',
  shadowOverlay: '8px 8px 0 0 #000000',
  shadowWindow: '8px 8px 0 0 #000000',

  unit: '5px',
  controlHeight: '42px',
  duration: '0s',
  easing: 'linear',
  press: 'translate(4px, 4px)',
}

/** Theme id → values. The first is the default, and the one the server renders. */
export const themes = { modern, pixel, glass, dos, paper, brutalist }

export type ThemeName = keyof typeof themes
export const defaultTheme: ThemeName = 'modern'

/** What the switcher shows for each theme. */
export const themeList: readonly {
  id: ThemeName
  label: string
  note: string
}[] = [
  {
    id: 'modern',
    label: 'Modern',
    note: 'Soft neutrals, underlined tabs, rounded corners, light shadows.',
  },
  {
    id: 'pixel',
    label: '8-bit',
    note: 'A 4px grid, stepped outlines, block shadows, a 16-colour palette, stepped motion.',
  },
  {
    id: 'glass',
    label: 'Liquid glass',
    note: 'A frosted window over a colour field, floating controls with lit edges, concentric radii, springy motion.',
  },
  {
    id: 'dos',
    label: 'DOS',
    note: 'Text mode: one size, the CGA palette, double-line frames, block shadows, no motion.',
  },
  {
    id: 'paper',
    label: 'Paper',
    note: 'Serif type, ink on cream, rules instead of boxes.',
  },
  {
    id: 'brutalist',
    label: 'Brutalist',
    note: 'One heavy border weight, hard offset shadows, flat primaries.',
  },
]

3. The theme schema and the system

The type every theme satisfies, and the system that checks the themes against it and emits them as CSS.

theme.ts
/**
 * What a theme has to supply. Tokens read these fields and nothing else, so a
 * theme that satisfies this type can restyle every stylesheet in the showcase.
 *
 * Every value is a CSS value: on the web each field becomes one custom
 * property, and the generated classes refer to it.
 */
export type Theme = {
  // ── Colour ────────────────────────────────────────────────────────────
  /** Behind the window. A full `background`, so it may be a gradient. */
  stage: string
  /** The window. A full `background`. */
  surface: string
  raised: string
  sunken: string
  /** Menus, dialogs and toasts. */
  overlay: string
  ink: string
  inkStrong: string
  inkMuted: string
  accent: string
  accentHover: string
  onAccent: string
  neutral: string
  neutralHover: string
  onNeutral: string
  danger: string
  dangerHover: string
  onDanger: string
  /** A `background-image` laid over `accent` and `danger`: light on a filled control. */
  sheen: string
  selected: string
  onSelected: string
  /** A row or quiet control under the pointer. */
  hover: string
  bar: string
  onBar: string
  head: string
  onHead: string
  ok: string
  onOk: string
  warn: string
  onWarn: string
  bad: string
  onBad: string
  /** The border of a panel. */
  line: string
  /** The border of a control. */
  lineStrong: string
  /** The border of a control that is filled with `accent` or `danger`. */
  lineFilled: string
  /** A divider between rows or columns. */
  rule: string
  /** The ring that separates overlapping avatars. */
  cutout: string
  track: string
  /** The filled part of a progress bar. A full `background`. */
  meter: string
  /** The well of a segmented control, and its selected option. */
  tray: string
  thumb: string
  onThumb: string
  /** The handle of a switch and of a slider. */
  knob: string
  /** The well behind the tabs, and the selected tab. */
  tabTray: string
  tabSelected: string
  onTabSelected: string
  /** The line under the selected tab, for themes that underline it. */
  tabIndicator: string
  /** Chart bars other than the newest one, which takes `accent`. */
  chart: string
  caret: string
  scrim: string

  // ── Type ──────────────────────────────────────────────────────────────
  fontBody: string
  fontDisplay: string
  fontData: string
  sizeBody: string
  sizeSmall: string
  sizeTitle: string
  sizeDisplay: string
  weightBody: string
  weightStrong: string
  weightDisplay: string
  leading: string
  /** `text-transform` for titles and controls. */
  caps: string
  labelCase: string
  labelTracking: string
  displayStyle: string
  displayTracking: string
  smoothing: string

  // ── Shape ─────────────────────────────────────────────────────────────
  radiusControl: string
  /** A part nested inside a control: a segment in its tray, a tab in its well. */
  radiusInner: string
  radiusTab: string
  radiusTabTray: string
  radiusPanel: string
  radiusWindow: string
  radiusAppBar: string
  radiusBadge: string
  radiusPill: string
  radiusChart: string
  /** A panel's top edge, and its other three: a theme may rule one edge only. */
  panelRule: string
  panelBorder: string
  panelBorderStyle: string
  controlBorder: string
  /** A field's bottom edge, and its other three. */
  fieldRule: string
  fieldBorder: string
  controlBorderStyle: string
  ruleWidth: string
  ruleStyle: string
  /** The line under the app bar. */
  barRule: string
  /** The thickness of the line under the selected tab. */
  tabRule: string
  meterHeight: string
  /** A complete `outline` for keyboard focus. */
  focusRing: string
  focusOffset: string

  // ── Depth ─────────────────────────────────────────────────────────────
  shadowPanel: string
  /** A checkbox, a switch track. */
  shadowControl: string
  shadowButton: string
  shadowField: string
  shadowThumb: string
  shadowKnob: string
  /** The current navigation item, project and tab. */
  shadowSelected: string
  shadowAppBar: string
  /** The well behind the tabs; themes that underline tabs draw the rule here. */
  shadowTabTray: string
  shadowOverlay: string
  /** The frame around the whole interface. */
  shadowWindow: string
  /** `backdrop-filter` for the window and for overlays. */
  blur: string

  // ── Density ───────────────────────────────────────────────────────────
  /** One spacing step; `gap: 3` is three of these. */
  unit: string
  controlHeight: string
  /** The space around the app bar: a theme may float it inside the window. */
  barInset: string
  tabTrayPad: string
  /** `align-self` for the tab list: across the column, or as wide as its tabs. */
  tabTrayAlign: string

  // ── Motion ────────────────────────────────────────────────────────────
  duration: string
  easing: string
  /** `transform` while a control is held down. */
  press: string
  disabledOpacity: string

  // ── Structure ─────────────────────────────────────────────────────────
  /** `display` for the window's title and status bars. */
  titleBar: string
  statusBar: string
  /** `content` around a button label and before the selected list item. */
  buttonOpen: string
  buttonClose: string
  bullet: string
  /** `content` after the page title: a text-mode cursor. */
  cursor: string
  /** `shape-rendering` for icons: a pixel theme turns smoothing off. */
  iconRendering: 'auto' | 'crispEdges'
}
system.ts
import { defineSystem } from '@toned/core'

import { themes } from './themes.ts'
import * as tokens from './tokens.ts'

/**
 * The showcase's own system. `themes` is checked against the schema the
 * tokens were written for: a theme that omits a field, or adds one no token
 * reads, does not compile.
 *
 * The build writes each declared theme as CSS custom properties: the first
 * on `:root`, and every one under `[data-theme='<name>']`. Generated classes
 * read those variables, so switching theme changes that one attribute.
 */
export const themeSystem = defineSystem({
  id: 'themes',
  tokens,
  conditions: { media: { md: 768, lg: 1080 } },
  themes,
})

export const { stylesheet } = themeSystem

4. The tokens

Each resolver turns a role into styles by reading the theme. Structural differences are tokens too: a theme decides whether the window has a title bar.

tokens.ts
import { defineToken, defineTokenFor, type WebInlineStyle } from '@toned/core'

import type { Theme } from './theme.ts'
import { sliderValue } from './vars.ts'

/**
 * The showcase's vocabulary. Stylesheets name a role (`fill: 'raised'`,
 * `edge: 'panel'`); each resolver reads the active theme to decide what that
 * role looks like. `defineTokenFor<Theme>()` limits a resolver to fields the
 * theme schema declares.
 */
const token = defineTokenFor<Theme, WebInlineStyle>()

/** Rules for pseudo-elements, keyed by the selector suffix. */
type PseudoRules = Record<string, Record<string, string | number>>

const roles = <const Map extends Record<string, unknown>>(map: Map) =>
  Object.keys(map) as Array<keyof Map & string>

/** `n` spacing steps of the theme's unit. */
const steps = (theme: Theme, n: number) => `calc(${theme.unit} * ${n})`
const scale = [0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8] as const

// ── Colour ──────────────────────────────────────────────────────────────

const fills = {
  stage: 'stage',
  surface: 'surface',
  raised: 'raised',
  sunken: 'sunken',
  overlay: 'overlay',
  accent: 'accent',
  'accent-hover': 'accentHover',
  neutral: 'neutral',
  'neutral-hover': 'neutralHover',
  danger: 'danger',
  'danger-hover': 'dangerHover',
  selected: 'selected',
  hover: 'hover',
  bar: 'bar',
  head: 'head',
  ok: 'ok',
  warn: 'warn',
  bad: 'bad',
  track: 'track',
  meter: 'meter',
  tray: 'tray',
  thumb: 'thumb',
  knob: 'knob',
  'tab-tray': 'tabTray',
  'tab-selected': 'tabSelected',
  chart: 'chart',
  ink: 'ink',
} as const satisfies Record<string, keyof Theme>

/** Fills the theme may light: its `sheen` is laid over the colour. */
const lit: readonly string[] = [
  'accent',
  'accent-hover',
  'danger',
  'danger-hover',
]

/** Background, by role. */
export const fill = token({
  values: [...roles(fills), 'none'],
  resolve: (value, theme) => ({
    background:
      value === 'none'
        ? 'transparent'
        : lit.includes(value)
          ? `${theme.sheen}, ${theme[fills[value]]}`
          : theme[fills[value]],
  }),
})

const inks = {
  default: 'ink',
  strong: 'inkStrong',
  muted: 'inkMuted',
  accent: 'accent',
  'on-accent': 'onAccent',
  'on-neutral': 'onNeutral',
  danger: 'danger',
  'on-danger': 'onDanger',
  'on-selected': 'onSelected',
  'on-thumb': 'onThumb',
  'on-tab-selected': 'onTabSelected',
  'on-bar': 'onBar',
  'on-head': 'onHead',
  'on-ok': 'onOk',
  'on-warn': 'onWarn',
  'on-bad': 'onBad',
  surface: 'surface',
} as const satisfies Record<string, keyof Theme>

/** Text and icon colour, by role. */
export const ink = token({
  values: roles(inks),
  resolve: (value, theme) => ({ color: theme[inks[value]] }),
})

// ── Shape ───────────────────────────────────────────────────────────────

/** Which outline a part carries. The theme chooses width, style and colour. */
export const edge = token({
  values: [
    'panel',
    'control',
    'filled',
    'field',
    'rule',
    'rule-top',
    'side',
    'bar',
    'tab',
    'tab-selected',
    'cutout',
    'none',
  ],
  resolve: (value, theme) => {
    if (value === 'panel')
      return {
        borderTopWidth: theme.panelRule,
        borderRightWidth: theme.panelBorder,
        borderBottomWidth: theme.panelBorder,
        borderLeftWidth: theme.panelBorder,
        borderStyle: theme.panelBorderStyle,
        borderColor: theme.line,
      }
    if (value === 'field')
      return {
        borderTopWidth: theme.fieldBorder,
        borderRightWidth: theme.fieldBorder,
        borderBottomWidth: theme.fieldRule,
        borderLeftWidth: theme.fieldBorder,
        borderStyle: theme.controlBorderStyle,
        borderColor: theme.lineStrong,
      }
    if (value === 'control' || value === 'filled')
      return {
        borderWidth: theme.controlBorder,
        borderStyle: theme.controlBorderStyle,
        borderColor: value === 'filled' ? theme.lineFilled : theme.lineStrong,
      }
    // A divider under or over a row, or beside a column.
    if (value === 'rule' || value === 'rule-top' || value === 'side')
      return {
        borderWidth:
          value === 'rule'
            ? `0 0 ${theme.ruleWidth} 0`
            : value === 'rule-top'
              ? `${theme.ruleWidth} 0 0 0`
              : `0 ${theme.ruleWidth} 0 0`,
        borderStyle: theme.ruleStyle,
        borderColor: theme.rule,
      }
    if (value === 'bar')
      return {
        borderWidth: `0 0 ${theme.barRule} 0`,
        borderStyle: theme.ruleStyle,
        borderColor: theme.rule,
      }
    // The line under a tab. Every tab reserves it, so selecting moves nothing.
    if (value === 'tab' || value === 'tab-selected')
      return {
        borderWidth: `0 0 ${theme.tabRule} 0`,
        borderStyle: 'solid',
        borderColor:
          value === 'tab-selected' ? theme.tabIndicator : 'transparent',
      }
    // Separates overlapping avatars from each other.
    if (value === 'cutout')
      return {
        borderWidth: '2px',
        borderStyle: 'solid',
        borderColor: theme.cutout,
      }
    return { borderWidth: 0, borderStyle: 'none', borderColor: 'transparent' }
  },
})

const corners = {
  control: 'radiusControl',
  'tab-tray': 'radiusTabTray',
  inner: 'radiusInner',
  tab: 'radiusTab',
  panel: 'radiusPanel',
  window: 'radiusWindow',
  'app-bar': 'radiusAppBar',
  badge: 'radiusBadge',
  pill: 'radiusPill',
  chart: 'radiusChart',
} as const satisfies Record<string, keyof Theme>

export const corner = token({
  values: [...roles(corners), 'none'],
  resolve: (value, theme) => ({
    borderRadius: value === 'none' ? 0 : theme[corners[value]],
  }),
})

const shadows = {
  panel: 'shadowPanel',
  control: 'shadowControl',
  button: 'shadowButton',
  field: 'shadowField',
  thumb: 'shadowThumb',
  knob: 'shadowKnob',
  selected: 'shadowSelected',
  'app-bar': 'shadowAppBar',
  'tab-tray': 'shadowTabTray',
  overlay: 'shadowOverlay',
  window: 'shadowWindow',
} as const satisfies Record<string, keyof Theme>

export const depth = token({
  values: [...roles(shadows), 'none'],
  resolve: (value, theme) => ({
    boxShadow: value === 'none' ? 'none' : theme[shadows[value]],
  }),
})

/** What a translucent surface does to whatever is behind it. */
export const frost = token({
  values: ['surface'],
  resolve: (_value, theme) => ({
    backdropFilter: theme.blur,
    WebkitBackdropFilter: theme.blur,
  }),
})

// ── Type ────────────────────────────────────────────────────────────────

export const type = token({
  values: ['body', 'small', 'label', 'control', 'title', 'display', 'data'],
  resolve: (value, theme) => {
    if (value === 'small') return { fontSize: theme.sizeSmall }
    if (value === 'label')
      return {
        fontSize: theme.sizeSmall,
        fontWeight: theme.weightStrong,
        textTransform: theme.labelCase,
        letterSpacing: theme.labelTracking,
      }
    if (value === 'control')
      return {
        fontFamily: theme.fontBody,
        fontSize: theme.sizeBody,
        fontWeight: theme.weightStrong,
        lineHeight: theme.leading,
        textTransform: theme.caps,
      }
    if (value === 'title')
      return {
        fontFamily: theme.fontDisplay,
        fontSize: theme.sizeTitle,
        fontWeight: theme.weightStrong,
        textTransform: theme.caps,
        lineHeight: 1.25,
      }
    if (value === 'display')
      return {
        fontFamily: theme.fontDisplay,
        fontSize: theme.sizeDisplay,
        fontWeight: theme.weightDisplay,
        fontStyle: theme.displayStyle,
        letterSpacing: theme.displayTracking,
        textTransform: theme.caps,
        lineHeight: 1.1,
      }
    if (value === 'data')
      return {
        fontFamily: theme.fontData,
        fontVariantNumeric: 'tabular-nums',
      }
    return {
      fontFamily: theme.fontBody,
      fontSize: theme.sizeBody,
      fontWeight: theme.weightBody,
      lineHeight: theme.leading,
      WebkitFontSmoothing: theme.smoothing,
    }
  },
})

export const weight = token({
  values: ['strong'],
  resolve: (_value, theme) => ({ fontWeight: theme.weightStrong }),
})

// ── Density ─────────────────────────────────────────────────────────────

export const gap = token({
  values: scale,
  resolve: (value, theme) => ({ gap: steps(theme, value) }),
})

export const pad = token({
  values: scale,
  resolve: (value, theme) => ({ padding: steps(theme, value) }),
})

export const padX = token({
  values: scale,
  resolve: (value, theme) => ({ paddingInline: steps(theme, value) }),
})

export const padY = token({
  values: scale,
  resolve: (value, theme) => ({ paddingBlock: steps(theme, value) }),
})

/** Fixed sizes that follow the theme's control height or spacing unit. */
export const size = token({
  values: [
    'control',
    'square',
    'avatar',
    'box',
    'switch',
    'thumb',
    'meter',
    'swatch',
    'spark',
  ],
  resolve: (value, theme) => {
    const square = (side: string) => ({
      width: side,
      height: side,
      flexShrink: 0,
    })
    if (value === 'control') return { minHeight: theme.controlHeight }
    // At least square: a theme may still draw brackets around the icon.
    if (value === 'square')
      return {
        minWidth: theme.controlHeight,
        height: theme.controlHeight,
        flexShrink: 0,
      }
    if (value === 'avatar') return square(steps(theme, 7))
    if (value === 'box') return square(steps(theme, 4.5))
    if (value === 'thumb') return square(steps(theme, 4))
    if (value === 'swatch') return square(steps(theme, 3))
    if (value === 'switch')
      return { width: steps(theme, 10), height: steps(theme, 6), flexShrink: 0 }
    // A small chart beside a figure.
    if (value === 'spark')
      return { width: steps(theme, 18), height: steps(theme, 8), minWidth: 0 }
    return { height: theme.meterHeight }
  },
})

/** Room a theme leaves around a part, or inside it. */
export const inset = token({
  values: ['app-bar', 'tab-tray'],
  resolve: (value, theme) =>
    value === 'app-bar'
      ? { margin: theme.barInset }
      : { padding: theme.tabTrayPad, alignSelf: theme.tabTrayAlign },
})

/** Pulls an avatar over the one before it. */
export const overlap = token({
  values: ['avatar'],
  resolve: (_value, theme) => ({ marginInlineStart: steps(theme, -1) }),
})

// ── Motion ──────────────────────────────────────────────────────────────

export const motion = token({
  values: ['themed'],
  resolve: (_value, theme) => ({
    transitionProperty:
      'background, color, border-color, box-shadow, transform, opacity',
    transitionDuration: theme.duration,
    transitionTimingFunction: theme.easing,
  }),
})

/** How a control moves while it is held down. */
export const press = token({
  values: ['down'],
  resolve: (_value, theme) => ({ transform: theme.press }),
})

/** Moves a switch's thumb to its on position. */
export const slide = token({
  values: ['on', 'off'],
  resolve: (value, theme) => ({
    transform: value === 'on' ? `translateX(${steps(theme, 4)})` : 'none',
  }),
})

export const focus = token({
  // `inside` is for a part in a scrolling row, which would clip a ring around it.
  values: ['ring', 'inside'],
  resolve: (value, theme) => ({
    outline: theme.focusRing,
    outlineOffset:
      value === 'inside'
        ? `calc(${theme.focusOffset} * -1 - 2px)`
        : theme.focusOffset,
  }),
})

/** A control that cannot be used right now. */
export const state = token({
  values: ['disabled'],
  resolve: (_value, theme) => ({
    opacity: theme.disabledOpacity,
    pointerEvents: 'none',
  }),
})

/** The text cursor of a field. */
export const caret = token({
  values: ['themed'],
  resolve: (_value, theme) => ({ caretColor: theme.caret }),
})

/** How icons are drawn. It is inherited, so the stage sets it once. */
export const icons = token({
  values: ['themed'],
  resolve: (_value, theme) => ({ shapeRendering: theme.iconRendering }),
})

// ── Structure ───────────────────────────────────────────────────────────

/**
 * Parts that exist in some themes only. The theme supplies the `display`
 * value, so a stylesheet can declare a title bar without knowing which themes
 * draw one.
 */
export const chrome = token({
  values: ['title-bar', 'status-bar'],
  resolve: (value, theme) => ({
    display: value === 'title-bar' ? theme.titleBar : theme.statusBar,
  }),
})

/**
 * Text a theme draws around a part: `[ Save ]`, a marker on the selection,
 * or a cursor after the page title.
 */
export const affix = token({
  values: ['button', 'bullet', 'cursor'],
  resolve: () => ({}),
  pseudoRules: (value, theme): PseudoRules =>
    value === 'button'
      ? {
          '::before': { content: theme.buttonOpen },
          '::after': { content: theme.buttonClose },
        }
      : value === 'bullet'
        ? { '::before': { content: theme.bullet } }
        : { '::after': { content: theme.cursor, color: theme.accent } },
})

const sliderTrack = (theme: Theme) => ({
  height: theme.meterHeight,
  // The part up to the thumb takes the accent; the rest is the track.
  background: `linear-gradient(${theme.accent}, ${theme.accent}) 0 0 / var(${sliderValue.inToken}, 0%) 100% no-repeat, ${theme.track}`,
  borderRadius: theme.radiusPill,
})

const sliderThumb = (theme: Theme) => ({
  appearance: 'none',
  boxSizing: 'border-box',
  width: steps(theme, 4.5),
  height: steps(theme, 4.5),
  background: theme.knob,
  borderWidth: theme.controlBorder,
  borderStyle: theme.controlBorderStyle,
  borderColor: theme.lineStrong,
  borderRadius: theme.radiusPill,
  boxShadow: theme.shadowKnob,
})

/**
 * Surfaces the browser draws and no element can stand in for: the parts of a
 * range input, the dialog backdrop and the text selection.
 */
export const native = token({
  values: ['slider', 'dialog', 'selection'],
  resolve: (value) =>
    value === 'slider' ? { appearance: 'none', background: 'transparent' } : {},
  pseudoRules: (value, theme): PseudoRules => {
    if (value === 'slider')
      return {
        '::-webkit-slider-runnable-track': sliderTrack(theme),
        '::-moz-range-track': sliderTrack(theme),
        '::-webkit-slider-thumb': {
          ...sliderThumb(theme),
          // Centres the thumb on the thinner track.
          marginTop: `calc((${theme.meterHeight} - ${steps(theme, 4.5)}) / 2)`,
        },
        '::-moz-range-thumb': sliderThumb(theme),
      }
    if (value === 'dialog') return { '::backdrop': { background: theme.scrim } }
    return {
      ' ::selection': { background: theme.ink, color: theme.surface },
    }
  },
})

// ── Layout ──────────────────────────────────────────────────────────────
// Arrangement does not change with the theme, so these read no theme fields.

const flows = {
  row: { display: 'flex', flexDirection: 'row' },
  column: { display: 'flex', flexDirection: 'column' },
  wrap: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap' },
  inline: { display: 'inline-flex', flexDirection: 'row' },
  block: { display: 'block' },
  cell: { display: 'table-cell' },
  hidden: { display: 'none' },
} as const

export const flow = defineToken({
  values: roles(flows),
  resolve: (value) => flows[value],
})

const alignments = {
  start: 'flex-start',
  center: 'center',
  end: 'flex-end',
  stretch: 'stretch',
  baseline: 'baseline',
} as const

export const align = defineToken({
  values: roles(alignments),
  resolve: (value) => ({ alignItems: alignments[value] }),
})

const justifications = {
  start: 'flex-start',
  center: 'center',
  end: 'flex-end',
  between: 'space-between',
} as const

export const justify = defineToken({
  values: roles(justifications),
  resolve: (value) => ({ justifyContent: justifications[value] }),
})

const flexes = {
  // Takes the remaining room, and may shrink below its content.
  fill: { flexGrow: 1, flexShrink: 1, flexBasis: '0%', minWidth: 0 },
  fixed: { flexGrow: 0, flexShrink: 0 },
  full: { width: '100%' },
  tall: { height: '100%' },
} as const

export const flex = defineToken({
  values: roles(flexes),
  resolve: (value) => flexes[value],
})

const grids = {
  stats: 'repeat(auto-fit, minmax(190px, 1fr))',
  fields: 'repeat(auto-fit, minmax(200px, 1fr))',
  chart: 'repeat(12, minmax(0, 1fr))',
} as const

export const tracks = defineToken({
  values: roles(grids),
  resolve: (value) => ({
    display: 'grid',
    gridTemplateColumns: grids[value],
  }),
})

const measures = {
  window: 1120,
  sidebar: 208,
  dialog: 440,
  menu: 200,
  chart: 120,
  meter: 64,
  percent: '4ch',
} as const

export const measure = defineToken({
  values: roles(measures),
  resolve: (value) =>
    value === 'chart'
      ? { height: measures.chart }
      : value === 'window'
        ? { width: '100%', maxWidth: measures.window }
        : value === 'meter' || value === 'percent'
          ? { minWidth: measures[value] }
          : value === 'dialog'
            ? { width: measures.dialog, maxWidth: 'calc(100vw - 32px)' }
            : value === 'menu'
              ? { minWidth: measures.menu }
              : { width: measures.sidebar, flexShrink: 0 },
})

const placements = {
  origin: { position: 'relative' },
  menu: { position: 'absolute', top: '100%', right: 0, zIndex: 3 },
  // A select's arrow, over the control's trailing edge.
  chevron: {
    position: 'absolute',
    right: 10,
    top: '50%',
    transform: 'translateY(-50%)',
    pointerEvents: 'none',
  },
} as const

/** Where a part sits relative to the part that owns it. */
export const place = defineToken({
  values: roles(placements),
  resolve: (value) => placements[value],
})

const overflows = {
  clip: { overflow: 'hidden' },
  'scroll-x': { overflowX: 'auto' },
  truncate: {
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
  },
  nowrap: { whiteSpace: 'nowrap' },
} as const

export const overflow = defineToken({
  values: roles(overflows),
  resolve: (value) => overflows[value],
})

export const textAlign = defineToken({
  values: ['left', 'center', 'right'] as const,
  resolve: (value) => ({ textAlign: value }),
})

/** Resets a native control so the tokens above decide how it looks. */
export const reset = defineToken({
  values: ['control', 'table', 'dialog', 'list'] as const,
  resolve: (value) =>
    value === 'control'
      ? { appearance: 'none', margin: 0, cursor: 'pointer' }
      : value === 'table'
        ? { borderCollapse: 'collapse', width: '100%' }
        : value === 'dialog'
          ? { margin: 'auto', maxHeight: 'calc(100vh - 32px)' }
          : { listStyle: 'none', margin: 0 },
})