# Default Theme

Style with bundled Geist colors and typography and Tailwind scales, without a config module.

## Overview

The opt-in `zyzz/default` entrypoint exports `style`, `variants`, `vars`, `appearance`, and `script` from one bundled config, plus its raw `tokens`. The helpers follow the [Core contracts](/docs/api/core) and accept the bundled token names. Core imports never load this data.

The package ships a compiled contract beside the module, which the bundler plugins and `Host` from `zyzz/node` read. `style` and `variants` exist only in compiled modules. The `sans` and `mono` stacks name Geist faces first, but the application loads the fonts.

Colors keep Geist light and dark pairs. Effect, radius, and query scales follow [Tailwind CSS 4.3.3](https://github.com/tailwindlabs/tailwindcss/blob/v4.3.3/packages/tailwindcss/theme.css). The [Variables explorer](/vars) lists every value.

```tsx title="Card.tsx"
// Bundled helpers replace a zyzz.config.ts module
import { style } from 'zyzz/default'

export function Card() {
  return (
    <article {...styles.card()}>
      <h2 {...styles.title()}>Personal account</h2>
    </article>
  )
}

namespace styles {
  export const card = style({
    borderRadius: 'lg',
    color: 'blue.700',
    padding: 4,
  })

  export const title = style({ typography: 'heading.20' })
}
```

## Walkthrough

The steps below style an account card, apply the theme to the document, and switch its color scheme. They target React on the web and assume the compiler setup from [Getting Started](/docs/introduction/getting-started).

### Style Elements

Import `style` from `zyzz/default`. Each property accepts token names from the groups it maps to, so `padding: 6` reads spacing and `typography` applies a Geist set.

```tsx title="AccountCard.tsx"
import { style } from 'zyzz/default'

export function AccountCard() {
  return (
    <article {...styles.card()}>
      <h2 {...styles.title()}>Personal account</h2>
      <p {...styles.body()}>alex@example.com</p>
    </article>
  )
}

namespace styles {
  // Secondary text from the gray scale
  export const body = style({ color: 'gray.900', typography: 'copy.14' })

  export const card = style({
    backgroundColor: 'background.surface',
    borderRadius: 'md',
    boxShadow: 'sm',
    color: 'foreground',
    display: 'grid',
    gap: 2,
    padding: 6,
  })

  export const title = style({ typography: 'heading.20' })
}
```

### Apply the Theme

`vars()` returns the class that holds the bundled values. Put it on `<html>`, and run `script()` early in `<head>` so a saved color scheme applies before the first paint.

```tsx title="Document.tsx"
import type { ReactNode } from 'react'
import { script, vars } from 'zyzz/default'

export function Document(props: Document.Props) {
  return (
    // Descendants read the bundled values
    <html lang="en" {...vars()} suppressHydrationWarning>
      <head>
        {/* Restores the saved scheme before the body renders */}
        <script dangerouslySetInnerHTML={{ __html: script() }} />
      </head>
      <body>{props.children}</body>
    </html>
  )
}

export declare namespace Document {
  type Props = { children: ReactNode }
}
```

### Switch the Scheme

`appearance.set` changes the document's color scheme and saves it under the `zyzz` storage key. The default config has one variable set, so `vars` and `appearance` accept only `colorScheme`.

```tsx title="SchemeToggle.tsx"
import { appearance } from 'zyzz/default'

export function SchemeToggle() {
  return (
    <button
      // Every color pair switches to its dark value
      onClick={() => appearance.set({ colorScheme: 'dark' })}
      type="button"
    >
      Dark
    </button>
  )
}
```

## Recipes

### Use Colors

The `color` group holds Geist families in steps `100` to `1000`. Most steps pair a light and a dark value, which compile to `light-dark()` and follow the selected color scheme.

* **`background`:** `primary` and `surface` page backgrounds.
* **`black` and `white`:** `#000` and `#fff` in both schemes.
* **Families:** `amber`, `blue`, `gray`, `grayAlpha`, `green`, `pink`, `purple`, `red`, and `teal`.
* **`foreground`:** Text color, with the same values as `gray.1000`.

```ts
import { style } from 'zyzz/default'

const panel = style({
  // Emits var(--z-default-color-background-surface, light-dark(#fff, #0a0a0a))
  backgroundColor: 'background.surface',
  color: 'foreground',
})
```

### Use Spacing

The `spacing` group is an explicit quarter-rem table, so a number resolves only when the table contains it. Fractional steps are absent, since dots separate token paths. Margins, padding, insets, gaps, and sizes resolve through it.

* **`0` to `12`:** Every step, from `0rem` to `3rem`.
* **`14` to `64`:** `14`, `16`, `20`, `24`, `28`, `32`, `36`, `40`, `44`, `48`, `52`, `56`, `60`, and `64`.
* **`72`, `80`, `96`:** `18rem`, `20rem`, and `24rem`.
* **`px`:** `1px`.

```ts
import { style } from 'zyzz/default'

const stack = style({
  gap: 'px',
  padding: 4,
  // Steps outside the table need the custom suffix
  marginTop: '13px !custom',
})
```

### Set Fonts

Font properties read only their own group, so `lineHeight` never falls back to spacing. Font weights are names, and a numeric weight needs the ` !custom` suffix.

* **`fontFamily`:** `mono`, `sans`, and `serif`. Only `mono` and `sans` name Geist faces first.
* **`fontSize`:** `xs` (`0.75rem`), `sm`, `base` (`1rem`), `lg`, `xl`, and `2xl` to `9xl` (`8rem`).
* **`fontWeight`:** `thin` (`100`), `extralight`, `light`, `normal`, `medium`, `semibold`, `bold`, `extrabold`, and `black` (`900`).
* **`letterSpacing`:** `tighter`, `tight`, `normal`, `wide`, `wider`, and `widest`.
* **`lineHeight`:** `tight` (`1.25`), `snug`, `normal`, `relaxed`, and `loose` (`2`).

```ts
import { style } from 'zyzz/default'

const code = style({
  fontFamily: 'mono',
  fontSize: 'sm',
  // Weights use names, such as medium for 500
  fontWeight: 'medium',
  lineHeight: 'relaxed',
})
```

### Size Elements

Radii, container sizes, and aspect ratios each map to their own properties. Widths try `spacing` before `container`, so `width: 'md'` reads the container size.

* **`aspect`:** `video` (`16 / 9`), for `aspectRatio`.
* **`container`:** `3xs` (`16rem`), `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, and `2xl` to `7xl` (`80rem`), for widths and inline sizes.
* **`radius`:** `xs` (`0.125rem`), `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, and `4xl` (`2rem`), for border radii.

```ts
import { style } from 'zyzz/default'

const media = style({
  aspectRatio: 'video',
  borderRadius: 'xl',
  // Emits var(--z-default-container-2xl, 42rem)
  maxWidth: '2xl',
})
```

### Apply Effects

Shadows, perspective, easing, and animations map to their CSS properties. The `animate` values reference keyframes that ship with the config.

* **`animate`:** `bounce`, `ping`, `pulse`, and `spin`, for `animation`.
* **`ease`:** `in`, `in-out`, and `out`, for `transitionTimingFunction`.
* **`perspective`:** `dramatic`, `near`, `normal`, `midrange`, and `distant`.
* **`shadow`:** `2xs` to `2xl`, and `inner`, for `boxShadow`.
* **`textShadow`:** `2xs` to `lg`.

```ts
import { style } from 'zyzz/default'

const spinner = style({
  // Emits var(--z-default-animate-spin, z-kid-zyzz_2d_spin 1s linear infinite)
  animation: 'spin',
  boxShadow: 'md',
})
```

### Reference Effects

The `blur`, `dropShadow`, and `insetShadow` groups map to no property. Reference them through `vars` inside a style declaration.

* **`blur`:** `xs` (`4px`) to `3xl` (`64px`).
* **`dropShadow`:** `xs` to `2xl`.
* **`insetShadow`:** `2xs`, `xs`, and `sm`.

```ts
import { style, vars } from 'zyzz/default'

const frosted = style({
  // References compose with other CSS values
  backdropFilter: `blur(${vars.blur.md})`,
  boxShadow: vars.insetShadow.sm,
})
```

### Use Query Aliases

The `breakpoint` and `container` groups name thresholds for `@media` and `@container` conditions. Each alias compiles to a literal query, so selecting a scope never moves a threshold.

* **`breakpoint`:** `sm` (`40rem`), `md` (`48rem`), `lg` (`64rem`), `xl` (`80rem`), and `2xl` (`96rem`).
* **`container`:** The container sizes, from `3xs` (`16rem`) to `7xl` (`80rem`).

```ts
import { style } from 'zyzz/default'

const grid = style({
  display: 'grid',
  // @media (width >= 48rem)
  '@media md': { gridTemplateColumns: '1fr 1fr' },
  // @container (width >= 24rem)
  '@container sm': { gap: 4 },
})
```

The [Conditions](/docs/guides/conditions#name-queries) guide covers below and range forms.

### Apply Typography

Each set follows [Geist typography](https://vercel.com/geist/typography) and expands to `fontFamily`, `fontSize`, `fontWeight`, `letterSpacing`, and `lineHeight`. A set's path names its group and its pixel size. Headings use negative letter spacing that grows with their size, and every other set resets it to `0px`.

* **`button`:** `12`, `14`, and `16`, at weight `500`.
* **`copy`:** `13`, `14`, `16`, `18`, `20`, and `24`, at weight `400`.
* **`heading`:** `14`, `16`, `20`, `24`, `32`, `40`, `48`, `56`, `64`, and `72`, at weight `600`.
* **`label`:** `12`, `13`, `14`, `16`, `18`, and `20`, at weight `400`.

```ts
import { style } from 'zyzz/default'

// 14px at weight 500, with 20px line height
const action = style({ typography: 'button.14' })
```

### Use Set Variants

A variant is a complete set at the same size, named by a third path segment. It changes only the expanded font fields, so Geist's descendant colors, capitalization, and tabular numbers stay explicit declarations.

* **`.mono`:** Geist Mono, on `copy` `13` and `label` `12` to `14`.
* **`.strong`:** Weight `550` on `copy` `14` to `24`, and `500` on `label` `12` to `16`.
* **`.subtle`:** Weight `500`, on `heading` `16`, `20`, `24`, and `32`.

```ts
import { style } from 'zyzz/default'

namespace styles {
  // Geist Mono at 14px
  export const code = style({ typography: 'label.14.mono' })

  export const emphasis = style({ typography: 'copy.16.strong' })
}
```

### Override Set Fields

A font declaration in the same block replaces that field of the set, wherever it appears. Weights use `fontWeight` token names, or the ` !custom` suffix for another value. A ` !important` suffix on the set marks every expanded declaration important.

```ts
import { style } from 'zyzz/default'

namespace styles {
  // The medium weight replaces the set's 400
  export const body = style({ fontWeight: 'medium', typography: 'copy.14' })

  // Every expanded declaration ends in !important
  export const pinned = style({ typography: 'label.13 !important' })
}
```

A numeric weight fails the type check, since it is neither a token name nor a custom value.

```ts
import { style } from 'zyzz/default'

// `500` is not a fontWeight token name
const body = style({ fontWeight: 500, typography: 'copy.14' })
// error: Type '500' is not assignable to type '500 & Expected<"light" | "black" | "thin" | "extralight" | "normal" | "medium" | "semibold" | "bold" | "extrabold" | `${string} !custom`>'.
// Type 'number' is not assignable to type 'Expected<"light" | "black" | "thin" | "extralight" | "normal" | "medium" | "semibold" | "bold" | "extrabold" | `${string} !custom`>'.
```

### Switch Sets Conditionally

Sets apply inside pseudo-classes, conditions, selectors, and variant choices, where they expand under the same rule.

```ts
import { style, variants } from 'zyzz/default'

namespace styles {
  export const link = style({
    typography: 'label.14',
    // Switches to the strong set on hover
    ':hover': { typography: 'label.14.strong' },
  })

  export const text = variants({
    variants: {
      // Each choice applies its own set
      size: { lg: { typography: 'copy.16' }, sm: { typography: 'copy.13' } },
    },
  })
}
```

### Reference Set Fields

Each set field is also a reference under `vars.typography`, for one declaration without the rest of the set.

```ts
import { style, vars } from 'zyzz/default'

// Reads only the heading's 32px size
const lead = style({ fontSize: vars.typography.heading[32].fontSize })
```

### Load Geist Fonts

The `sans` and `mono` stacks, and every typography set, name `Geist` or `"Geist Mono"` before system fallbacks, and load no fonts. Declare the faces with `fontFace` from `zyzz/web`, as the [Fonts & Typography](/docs/guides/typography) guide shows.

```ts title="src/fonts.ts"
import { fontFace } from 'zyzz/web'

fontFace({
  fontDisplay: 'swap',
  // Matches the family name the sans stack uses
  fontFamily: 'Geist',
  src: 'url("./Geist.woff2") format("woff2")',
})
```

### Read Raw Values

`tokens` is the literal record behind the config, for code outside CSS such as a chart. Its values are fixed, while a `vars` reference follows the active scope and works only inside a style declaration.

```ts
import { tokens } from 'zyzz/default'

// '#0072f5'
const accent = tokens.color.blue[700]
// { dark: '#ededed', light: '#171717' }
const text = tokens.color.foreground
```

## More

[Themes & Tokens](/docs/guides/themes)

Define custom token sets, add themes, and select color schemes per scope.

[Fonts & Typography](/docs/guides/typography)

Load font faces, name font tokens, and define custom typography presets.

[Conditions](/docs/guides/conditions)

Respond to viewport size, container size, browser state, and other elements.

[Variables](/vars)

Browse every bundled color, spacing, typography, and effect token value.
