# Why Zyzz

Standard CSS with design rules that developers and agents can check.

Most styling drift looks plausible. A hex value one shade off the brand color, `13px` where the spacing scale has `12px`, or a third button size that no design specified will compile, render, and often pass review. Coding agents produce these values faster than reviewers can catch them.

Zyzz turns the design system into checks. Design values live in a typed config, styles use standard CSS, and the type checker, compiler, and linter report values outside the system before review.

## Checkable Agent Rules

A Zyzz config names each design value and groups it by domain, such as color or spacing. [Property mappings](/docs/guides/themes#customize-property-mappings) connect each domain to the CSS properties that accept its names.

```ts title="zyzz.config.ts"
import { defineConfig } from 'zyzz'

export const { style } = defineConfig({
  vars: {
    color: { brand: { dark: '#8cf', light: '#06c' } },
    spacing: { md: '1rem', sm: '0.5rem' },
  },
})
```

The configured `style` helper offers those names as completions, so editors and agents read the design system from the types:

```ts title="button.ts"
import { style } from './zyzz.config.js'

export namespace styles {
  export const button = style({
    color: 'brand',
    padding: '',
    // completions: md, sm
  })
}
```

### Rejected Values

Values outside the system fail the type check at the declaration, and the diagnostic lists the values that property accepts. `#0070f3` is not a configured color, and the same value passes with the ` !custom` suffix:

```ts title="button.ts"
import { style } from './zyzz.config.js'

export namespace styles {
  export const button = style({
    color: 'brand',
    padding: 'md',
    backgroundColor: '#0070f3',
    // error: Type '"#0070f3"' is not assignable to type '"#0070f3" & Expected<"brand" | `${string} !custom`>'.
    // Type 'string' is not assignable to type 'Expected<"brand" | `${string} !custom`>'.
    borderColor: '#0070f3 !custom',
  })
}
```

The build rejects the same declarations with a message that names the fix: use a configured token, or add the ` !custom` suffix. The suffix accepts a literal CSS value for one declaration, so a deliberate exception stays possible and searchable in review:

```ts
style({ marginTop: '7px !custom' })
```

### Integrate with Oxlint

[Oxlint](https://oxc.rs/docs/guide/usage/linter.html) is a JavaScript and TypeScript linter from the Oxc project, written in Rust. It reads ESLint-style rule configuration and loads JavaScript plugins. Zyzz ships one, [`zyzz/oxlint`](/docs/api/oxlint), for rules that types cannot express:

* `zyzz/valid-styles`: unknown properties, malformed value markers, and empty fallback arrays
* `zyzz/no-conflicting-props`: `className` or `style` props that a style spread would overwrite
* `zyzz/no-unused`: style and variant definitions that nothing applies
* `zyzz/use-logical-properties`: physical properties such as `marginLeft` where a logical property fits
* `zyzz/restricted-properties`: properties and values the project restricts, with a reason in each diagnostic

A restriction carries the project's own reason, and the linter reports that reason at the declaration. This configuration bans `zIndex` and points to the alternative:

```json title=".oxlintrc.json"
{
  "jsPlugins": [{ "name": "zyzz", "specifier": "zyzz/oxlint" }],
  "rules": {
    "zyzz/restricted-properties": [
      "error",
      { "zIndex": { "reason": "Use the <Layer> component for stacking." } }
    ]
  }
}
```

```ts title="modal.ts"
import { style } from 'zyzz'

export namespace styles {
  export const modal = style({
    position: 'fixed',
    zIndex: 9999,
    // zyzz(restricted-properties): Property 'zIndex' is restricted. Use the <Layer> component for stacking.
  })
}
```

These checks run in tools that agents already call: the type checker, the build, and the linter. Each failure identifies the declaration to fix, so an agent can correct its own change before a person reviews it.

## Built on CSS

Zyzz adds no styling language of its own. Properties use their CSS names in camelCase, values keep CSS syntax and units, and selectors, queries, and at-rules appear as written. What developers and agents already know about CSS applies directly, and MDN remains the reference.

```tsx title="Card.tsx"
import { style } from 'zyzz'

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

namespace styles {
  export const card = style({
    border: '1px solid light-dark(#ddd, #333)',
    borderRadius: '0.75rem',
    containerType: 'inline-size',
    padding: '1rem',
    ':focus-within': { outline: '2px solid currentColor' },
    '@media (min-width: 48rem)': { padding: '1.5rem' },
  })

  export const title = style({
    fontSize: '1.25rem',
    margin: 0,
    '@container (min-width: 32rem)': { fontSize: '1.5rem' },
  })
}
```

The emitted stylesheet contains the same rules. Queries, inheritance, specificity, and the cascade work as CSS defines them, without a runtime style engine or viewport listeners from Zyzz.

The types follow CSS too. Every property in MDN's CSS data has typed values, checked against MDN's published grammar, so `display: 'flx'` fails the type check. The [CSS conformance](https://github.com/wevm/zyzz/blob/main/test/conformance/README.md) inventory tracks that coverage.

## Centralized Design Decisions

Components reference token names, and the config owns their values. A rebrand, a dark theme, or a tighter spacing scale changes the config, and components follow without edits. A dark theme, for example, pairs each color with a dark value:

```diff title="zyzz.config.ts"
 import { defineConfig } from 'zyzz'
 
 export const { style, vars } = defineConfig({
   vars: {
     color: {
-      surface: '#fff',
-      text: '#111',
+      surface: { dark: '#111', light: '#fff' },
+      text: { dark: '#eee', light: '#111' },
     },
   },
 })
```

```tsx title="Document.tsx"
import { style, vars } from './zyzz.config.js'

export function Document() {
  return (
    <html {...vars({ colorScheme: 'light dark' })}>
      <body {...styles.body()}>Content</body>
    </html>
  )
}

namespace styles {
  export const body = style({ backgroundColor: 'surface', color: 'text' })
}
```

Each color pair compiles to a custom property, such as `--z-color-surface: light-dark(#fff, #111)`. Components contain no dark-mode branches, and `colorScheme: 'light dark'` follows the system preference without a script or provider.

Named variable sets work the same way. `vars({ set: 'mint' })` on any element changes the values its subtree inherits, while component styles and compiled rules stay the same. [Themes & Tokens](/docs/guides/themes) covers sets, scopes, and saved preferences.

## State-Based Styling

Interactive state already lives in the DOM as attributes such as `aria-selected`, `aria-expanded`, and `data-state`. Zyzz styles those attributes with selectors instead of toggling class names. The accessible state and the visual state then come from the same attribute.

```tsx title="Tab.tsx"
import { style } from 'zyzz'

export function Tab(props: Tab.Props) {
  return (
    <button aria-selected={props.selected} role="tab" {...styles.tab()}>
      {props.label}
    </button>
  )
}

export declare namespace Tab {
  type Props = { label: string; selected: boolean }
}

namespace styles {
  export const tab = style({
    borderBottom: '2px solid transparent',
    '&[aria-selected="true"]': { borderBottomColor: 'currentColor' },
  })
}
```

A tab cannot look selected while assistive technology reports it as unselected. Tests and browser agents can query the same attribute that the style matches.

### Variants

Design choices such as size or tone are variants. The definition lists the choices and defaults, and the component's props type derives from it, so an unsupported choice fails at the call site.

```tsx title="Button.tsx"
import { type Props, variants } from 'zyzz'

export function Button(props: Props.Variants<typeof styles.button>) {
  return <button {...styles.button(props)}>Save</button>
}

const example = <Button size="xl" /> // Type error: size accepts 'md' or 'sm'

namespace styles {
  export const button = variants({
    base: { display: 'inline-flex' },
    defaultVariants: { size: 'md' },
    variants: {
      size: {
        md: { padding: '1rem' },
        sm: { padding: '0.5rem' },
      },
    },
  })
}
```

Each choice compiles ahead of time, and a data attribute such as `data-size="sm"` selects it on the rendered element. [Variants](/docs/guides/variants) covers compound rules and choices selected by CSS conditions.

## Next Steps

[Getting Started](/docs/introduction/getting-started)

Connect Zyzz to Vite, Next.js, and other build tools.

[Thinking in Zyzz](/docs/introduction/thinking-in-zyzz)

Organize styles beside components and share design values.

[Comparisons](/docs/introduction/comparisons)

Compare the same components across other styling libraries.
