---
title: Styles and tokens
description: A StyleDef is a token set plus selector rules; tokens become CSS custom properties and rules apply to every matching node.
status: shipped
url: https://getairframe.com/framewerk/core/styles-and-tokens
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# Styles and tokens

A **StyleDef** is a token set plus rules: tokens become CSS custom properties any node can reference with `var(--token)`, and rules apply properties to every node matching a selector.

## Examples

A minimal style with a heading rule and a primary button:

**A style with rules**

```json
{
  "formatVersion": "1.0",
  "styles": [
    {
      "id": "sample-light",
      "name": "Sample Light",
      "colorMode": "light",
      "tokens": { "--brand-primary": "#7C5CFF", "--surface": "#FFFFFF" },
      "rules": [
        { "selector": "text[variant=heading]", "properties": { "fontWeight": "700", "fontSize": "22px" } },
        { "selector": "button[variant=primary]", "properties": { "background": "var(--brand-primary)", "color": "#FFFFFF" } }
      ]
    }
  ],
  "screens": [
    {
      "id": "home",
      "style": "sample-light",
      "layout": {
        "_id": "root",
        "type": "column",
        "gap": 16,
        "padding": 24,
        "children": [
          { "_id": "h", "type": "text", "variant": "heading", "content": "Welcome" },
          { "_id": "go", "type": "button", "variant": "primary", "content": "Continue" }
        ]
      }
    }
  ]
}
```

A style with named keyframes reachable from `animate`:

**Style keyframes**

```json
{
  "formatVersion": "1.0",
  "styles": [
    {
      "id": "brand",
      "name": "Brand",
      "tokens": {},
      "rules": [],
      "keyframes": {
        "blob": "0%, 100% { border-radius: 60% 40% 30% 70%; } 50% { border-radius: 30% 60% 70% 40%; }"
      }
    }
  ],
  "screens": [
    {
      "id": "home",
      "style": "brand",
      "layout": {
        "_id": "root",
        "type": "column",
        "gap": 16,
        "padding": 24,
        "children": [
          { "_id": "blob", "type": "shape", "variant": "circle", "width": 64, "height": 64, "background": "var(--accent)", "animate": "blob" },
          { "_id": "h", "type": "text", "variant": "heading", "content": "Brand" }
        ]
      }
    }
  ]
}
```

## Anatomy

**Tokens** become CSS custom properties. Any node prop may reference one with `var(--token)`.

**Rules** apply properties to every node matching a selector. Selectors are `nodeType` optionally filtered by a prop: `text`, `button[variant=primary]`, `icon[name=coffee]`. A selector ending in `:hover`, `:active` or `:focus` styles that interaction state theme-wide.

**Keyframes** a style names are motion `animate` can reach when the preset registry has no word for it: keys are names, values are `@keyframes` *bodies*. The registry resolves first (a style cannot shadow `slide-up`); a style keyframe runs once for 1000ms at `ease-in-out` unless the node says otherwise; a body that will not parse is a validation error.

The style in force resolves through a cascade: an explicit override → `screen.style` → `project.defaultStyle` → the built-in default.

### Built-in tokens

The default style defines these, and your tokens override them by name:

| Token | Default | Purpose |
| --- | --- | --- |
| `--bg-primary` | `#f0f0f0` | Screen background |
| `--bg-surface` | `#ffffff` | Cards, sheets |
| `--bg-sidebar` | `#f7f7f7` | Sidebars |
| `--bg-overlay` | `#e8e8e8` | Overlays |
| `--bg-accent-light` | `#e8eeff` | Accent tint |
| `--border` | `#d0d0d0` | Borders |
| `--text-primary` | `#2a2a2a` | Body text |
| `--text-muted` | `#888888` | Secondary text |
| `--accent` | `#4a7cff` | Accent |
| `--btn-primary-bg` | `#333333` | Primary button fill |
| `--font-body` | `'DM Sans', sans-serif` | Body font |
| `--font-heading` | `'Inter', sans-serif` | Heading font |
| `--screen-padding` | `16px` | Screen gutter |

### Background tokens

The `background` prop accepts these semantic names, which resolve to the matching `--bg-*` token: `surface`, `sidebar`, `background`, `overlay`, `accent-light`, `none`. It also accepts a raw hex value, a CSS gradient string (`linear-gradient(...)`, `radial-gradient(...)`, `conic-gradient(...)`), or a `url(...)` image fill — gradients and image fills route to the CSS `background` shorthand, and gradient-scrim composites work (`"linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(hero.jpg) center/cover"`).

### Brands

A `BrandDef` captures identity — palette, typography, shape, tone, language rules — that a `StyleDef` can be generated *from*. Styles record their origin in `brandId`, so a regenerated style is traceable to the brand it came from. Brands are optional; a style stands alone perfectly well.

## Stability

Style tokens, rules, keyframes and the built-in token names are part of the format contract. Adding a token or rule capability is additive; renaming a built-in token or changing how `var(--token)` resolves is breaking. Two-face styles (`modes.light` / `modes.dark`) are covered on [Theming & modes](../rendering/theming-and-modes.md).
