# Design system and theming

A theme isn't a color. It's a **system of chained decisions**: you pick a brand, and from it comes a derived palette, a per-mode semantics (light/dark), a typography scale, a spacing grid, a radius scale, an elevation set, and a motion vocabulary. Changing "the app's color" actually means pulling on one end of that chain — and the whole point of a design system is to guarantee the rest of the chain stays coherent.

In Fayz, the manifest's `theme` block is the **condensed expression** of that system. You declare very little; the platform runtime fills in the rest consistently. Before you write that block, it's worth understanding the full system it summarizes.

## The eight token families

A mature design system organizes its decisions into families. The values below come from Fayz's official design system — they're a concrete example of how much richness each family carries.

| Family | What it decides | Example (Fayz design system) |
| --- | --- | --- |
| **Brand + tints** | the signature color and its derived variations | `ignite #2FDD4B` → `deep #119A27`, `soft #DFFBE3`, `bg #F2FDF4` |
| **Neutrals** | the gray scale that carries text and surfaces | `paper #FAFAF8` → `near-black #0B0F0E` (12 steps) |
| **Secondaries** | occasional accents, used sparingly | `yellow #F2DB0F`, `cyan #17C1EA`, `coral #FF6B5C` |
| **Per-mode semantics** | the bg / surface / border / text mapping per theme | light uses `paper` + dark text; dark flips to `near-black` + white text |
| **Typography** | families and hierarchy by weight | `Outfit` (display), `DM Sans` (body), `JetBrains Mono` (code) |
| **Spacing** | the grid that aligns everything | a **4px** grid (4, 8, 12, 16, 24, 32…) |
| **Radius** | the personality of the corners | `xs 4px` → `sm 8px` → `md 14px` → `lg 22px` → `pill 9999px` |
| **Elevation + motion** | shadows, the signature glow, and the timing | `shadow-sm` → `shadow-glow` (green halo); `ease-out cubic-bezier(0.22,1,0.36,1)`, `240ms` |

There's a ninth piece too, optional but characteristic of the brand: **frosted glass** (`rgba(255,255,255,0.55)` + `blur 24px`) and the **aurora** — a green `radial-gradient` bleeding out behind the hero. These are recipes, not loose colors: each one combines background, border, blur, and glow into a single named token.

{% callout type="tip" %}
These docs use that very system: the body is **Inter**, code is **JetBrains Mono**, the primary button carries the ignite-green glow, and the hero carries the aurora. A well-built design system applies just as well to an admin panel as to a docs page.
{% /callout %}

## From one color to a system

The point of a design system shows up when you follow **one** decision all the way through. Take the brand `ignite #2FDD4B`:

- As a **fill** (a button, a block), the vivid green `#2FDD4B` works with near-black text on top — contrast stays high because the green is light.
- As **link text** on a white background, `#2FDD4B` fails contrast. So the system uses `deep #119A27` — the same brand, darkened — to keep the link readable.
- In **dark mode**, the logic flips: the background becomes `near-black`, the text becomes white, and the same green now glows harder (the aurora's opacity goes from `0.55` to `0.65`).
- For **selection and hover halos**, `soft #DFFBE3` steps in; for subtle surface accents in light mode, `bg #F2FDF4`.

Notice that reasoning about **contrast and accessibility** is part of the design system, not an afterthought. One brand decision turned into four tokens with distinct roles, each chosen to stay readable in the right context. That's why hand-declaring dozens of colors is fragile — and deriving them from a single root is robust.

## What the Fayz runtime does with your theme today

In the manifest you write very little:

```json
{
  "theme": {
    "brand": "teal",
    "radius": "lg",
    "mode": "system"
  }
}
```

- **`brand`** — the brand color. Ready-made values: `blue`, `violet`, `green`, `orange`, `red`, `pink`, `teal`.
- **`radius`** — the corner rounding: `none`, `sm`, `md`, `lg`, `full`.
- **`mode`** — the color mode: `light`, `dark`, or `system`.

Today `fayz doctor` validates the **structure** of the manifest (the presence and shape of the `theme` block), but not yet the *values* of these enums — validation that `brand`/`radius`/`mode` fall inside those lists is on the way (an SDK fix is in PR).

From there, the shell's theme engine (`createTheme` / `applyTheme`, in `@fayz-ai/saas`) does the derivation. Verified in the code, from a single brand color it produces:

- **`primary`** = the brand, with a **white `primaryForeground`** — readable text over the colored fill.
- **`ring`** = the brand — the focus ring inherits the identity.
- **`accent`** = the brand with the **hue rotated ~50°** (same saturation and lightness), automatically generating a second harmonic tone.
- An optional **colored sidebar**: the engine tints the rail's background with the brand, forces white text, darkens the border by ~12% and the active item by ~15% lightness, and mutes the secondary icons (`hue 35% 82%`) — all derived from the same root so text stays readable over the saturated rail.

Everything else — neutrals, surfaces, borders, `success`/`warning`/`destructive` semantics, the radius scale, the shadows, and the typography — comes from a **curated base preset** (the default is the classic admin) that your brand slots into. `applyTheme` then writes each value as a **CSS variable** on the document (`--primary`, `--ring`, `--accent`, `--sidebar`, `--button-radius`, `--font-family`, `--shadow-sm/md/lg`, and the glass tokens like `--surface-backdrop-filter`). In other words: your brand is **one input** into a larger, already-consistent token system — exactly the design system model.

{% callout type="info" %}
The derivation happens in the **Fayz platform runtime**, which consumes the `theme` block to paint the whole shell. In the scaffold's local preview — today an anchor screen — the palette isn't applied yet. That's why you validate this step with `doctor` (valid config) while the full visual result shows up in the runtime. Never count on the brand rendering in the isolated local preview.
{% /callout %}

## Where this is headed {% badge status="experimental" /%}

Today the manifest exposes three decisions (brand, radius, mode). The direction is to bring in the richer families from the official design system — the type scale, elevation, frosted glass, and motion — as **customization seams**, without bloating the manifest. When you outgrow what config can solve, the path already exists: levels 5–7 of the ladder in [Customization](/en/docs/apps/customization) let you register your own tokens and components, referenced as data, without forking the SDK.

## Recommended flow: the design system before the manifest

The order that works best is **outside-in**:

1. **Design the system as an artifact first** — a `tokens.css` with the eight families, the way Fayz's own design system was built in an AI design session. Having the colors, tints, typography, and elevation written in one place forces the contrast decisions before any code.
2. **Summarize it in the manifest** — the `theme` block becomes the condensed expression of that artifact: the brand root, the corner personality, and the mode strategy. The runtime derives the rest in the direction your artifact already set.
3. **Validate with doctor** — and iterate on the artifact when you need more richness, promoting tokens into the customization seams as it grows.

That way the manifest is never where you "discover" the theme — it's where you **declare** it, after you've already thought through the whole system.

See also: the [full token reference](/en/docs/reference/theme-tokens) — a quick lookup for the eight families and the CSS variables the runtime writes.

---

Next: the seams for going beyond config in [Customization](/en/docs/apps/customization).
