Mixins / foldkit-mixins/theme

Theme

namespace · packages/mixins/src/theme.ts:27

const Theme = {
  ...Core.Theme,
  root,
  scoped,
  tokens,
  ladder,
  role,
  nested,
  raise,
  raisedSurface,
  breakpointWidths,
  inContainer,
  oklch,
  schemeScript,
  chooseScheme,
  currentScheme,
}

In the guides

Members

Theme.define

function · packages/mixins/src/theme/core.ts:16

define<T extends ThemeTokens>(tokens: T): Readonly<T>

Theme.ref

function · packages/mixins/src/theme/core.ts:35

ref<T extends ThemeTokens>(theme: T): Refs<T>

The theme's tokens as references: Theme.ref(theme).surface.overt is var(--fk-surface-overt), and a group or name the theme lacks is a type error. Only the names are read, never the values. Built once per theme.

Theme.variables

function · packages/mixins/src/theme/core.ts:54

variables<T extends ThemeTokens>(theme: T): StyleValue

Theme.lightDark

function · packages/mixins/src/theme/core.ts:69

lightDark(light: string, dark: string): string

A token that follows the user's color scheme with CSS light-dark(): no JavaScript and no Model field. A theme the user chooses is the other case, and belongs in the Model as a class on the root; variables compiles both.

Theme.compose

function · packages/mixins/src/theme/core.ts:82

compose<A extends ThemeTokens, B extends ThemeTokens>(
  base: A,
  over: B,
): Readonly<Merge<A, B>>

Themes merged at definition time, later tokens winning within a group.

Theme.root

function · packages/mixins/src/theme/root.ts:30

root(theme: ThemeTokens, options?: RootOptions): StyleValue

Theme.scoped

function · packages/mixins/src/theme/root.ts:49

scoped<T extends ThemeTokens>(
  _theme: T,
  selector: string,
  overrides: Overrides<T>,
): StyleValue

Token overrides under selector: a named theme the Model selects with a root attribute (:root[data-theme="ocean"]), a scheme the user chose (:root[data-color-scheme="dark"]), or a subtree (.marketing). Which one is active is the Model's fact; this only says what it means.

theme is the theme being overridden and is read only for its type, so a group or token it lacks is a type error rather than a variable nothing reads.

Theme.tokens

const · packages/mixins/src/theme/tokens.ts:27

tokens: Readonly<{ knob: { density: string; 'radius-factor': string; motion: string; travel: string; step: string; 'shadow-strength': string; }; space: { '3xs': string; '2xs': string; xs: string; sm: string; md: string; lg: string; xl: string; '2xl': string; '3xl': string; }; radius: { xs: string; sm: string; md: string; lg: string; xl: string; full: string; }; font: { body: string; heading: string; mono: string; }; size: { xs: string; sm: string; md: string; lg: string; xl: string; '2xl': string; '3xl': string; '4xl': string; }; leading: { tight: string; snug: string; normal: string; relaxed: string; }; weight: { normal: string; medium: string; semibold: string; bold: string; }; motion: { fast: string; moderate: string; slow: string; 'fast-exit': string; 'moderate-exit': string; 'slow-exit': string; ease: string; 'ease-exit': string; settle: string; spring: string; }; control: { height: string; 'height-compact': string; pad: string; 'pad-compact': string; 'row-pad': string; 'row-pad-compact': string; gap: string; 'gap-compact': string; inset: string; 'inset-compact': string; icon: string; 'icon-compact': string; }; type: { 'display-size': string; 'display-size-compact': string; 'display-leading': string; 'display-leading-compact': string; 'title-size': string; 'title-size-compact': string; 'title-leading': string; 'title-leading-compact': string; 'subtitle-size': string; 'subtitle-size-compact': string; 'subtitle-leading': string; 'subtitle-leading-compact': string; 'body-size': str …;

Theme.ladder

function · packages/mixins/src/theme/tokens.ts:181

ladder(name: Rung): string

A control's dimension on the size ladder: name's default end blended toward its compact one by the step knob, and spacing multiplied by density. The blend is written here, not in a token, because a custom property resolves where it is declared: a token would read the step at :root, while this expression reads it on the element that uses it, so --fk-knob-step: 1 on a toolbar makes everything in it compact, a menu it opens included while the panel stays in the page (an anchor's portal: false); a portaled panel takes only the size its recipe chooses. A step of -0.5 is shadcn's large size, 40px.

Theme.role

function · packages/mixins/src/theme/tokens.ts:203

role(name: Role): {
  readonly fontSize: string
  readonly lineHeight: string
}

Text in role: its size and leading on the size ladder, read on the element like ladder, so a compact region's text steps down with its controls.

Theme.nested

function · packages/mixins/src/theme/tokens.ts:192

nested(outer: string, inset: string, border?: string): string

The radius of a rounded surface inset in another, so their corners are concentric: the outer radius less the inset between them and the outer surface's border, never below zero. Past a large radius, or with uneven gaps, the two read as independent and may be tuned by eye.

Theme.raise

function · packages/mixins/src/theme/raise.ts:43

raise(offset: number): StyleValue

An element offset levels above the raised element holding it, or the page: popovers and menus are 2, dialogs 4. Paint it with raisedSurface.

Theme.raisedSurface

function · packages/mixins/src/theme/raise.ts:54

raisedSurface(): string

The background of a raised element at its level, from Theme.oklch's base surface. In a light scheme the first two steps go toward white and the rest stay there, so shadow alone carries elevation; in a dark scheme each level is an even step lighter (knob.raise-dark). Written as an expression, not a token, because the level is the element's own and a token resolves at :root.

Theme.breakpointWidths

function · packages/mixins/src/theme/tokens.ts:222

breakpointWidths(theme: {
  readonly breakpoint: {
    readonly [name: string]: string
  }
}): Readonly<Record<string, number>>

Each breakpoint query's min-width in pixels (rem at 16), so CSS and the Model agree on what md means. A query that is not a plain min-width is refused: it has no single threshold. A container's is its container's.

Theme.inContainer

function · packages/mixins/src/theme/tokens.ts:250

inContainer<Breakpoints extends Readonly<Record<string, string>>>(
  name: string,
  breakpoints: Breakpoints,
): {
  readonly [K in keyof Breakpoints]: string
}

The same breakpoints measured on a named container rather than the window: Theme.inContainer('page', Theme.tokens.breakpoint) gives md as '@container page (min-width: 48rem)', for Style.responsive and a responsive look. An element must be that container (container: page / inline-size) for them to match.

Theme.oklch

function · packages/mixins/src/theme/oklch.ts:161

oklch(knobs: OklchKnobs): Readonly<{ knob: { 'accent-h': string; 'accent-c': string; 'accent-l': string; 'accent-c-dark': string; 'accent-l-dark': string; 'neutral-h': string; 'secondary-shift': string; 'tertiary-shift': string; 'surface-c': string; 'surface-c-dark': string; 'surface-contrast': string; 'contrast-factor': string; 'base-l': string; 'base-l-dark': string; 'success-h': string; 'warning-h': string; 'error-h': string; 'info-h': string; 'syntax-c': string; 'popup-bold': string; 'popup-invert': string; 'popup-alpha': string; 'popup-blur': string; 'state-hover': string; 'state-tint': string; 'state-tint-dark': string; 'image-edge': string; 'raise-dark': string; }; hue: { accent: string; secondary: string; tertiary: string; neutral: string; }; surface: { base: string; muted: string; subtle: string; default: string; overt: string; bedrock: string; }; text: { default: string; muted: string; subtle: string; overt: string; link: string; 'link-hover': string; }; outline: { subtle: string; default: string; overt: string; focus: string; }; accent: { default: string; hover: string; active: string; subtle: string; outline: string; 'on-fill': string; ink: string; }; secondary: { default: string; hover: string; active: string; subtle: string; outline: string; 'on-fill': string; ink: string; }; tertiary: { default: string; hover: string; active: string; subtle: string; outline: string; 'on-fill': string; ink: string; }; shadow: { color: string; }; state: { hover: string; tint: string; 'tint-hover': string; …;

Theme.schemeScript

function · packages/mixins/src/theme/scheme.ts:24

schemeScript(key?: string): string

The text of a <script> for the document's head, before any stylesheet draws: it applies the scheme kept under key, so a reader who chose dark never sees one light frame. Storage that throws (a private window) or holds anything else leaves the stylesheet's choice.

Theme.chooseScheme

function · packages/mixins/src/theme/scheme.ts:40

chooseScheme(scheme: Scheme, key?: string): Effect.Effect<void>

Applies scheme to the root element and keeps it under key for the next visit, as a Command's effect. 'system' removes both. Storage that throws still applies the scheme to this page.

Theme.currentScheme

function · packages/mixins/src/theme/scheme.ts:29

currentScheme(): Scheme

The scheme the root element shows now: what schemeScript or chooseScheme applied.