# Theming

Your brand, colors, corners and gutters are CSS variables. The Theme page (https://grani.arseni.work/theme) writes them for you; this page says what you get, where it goes and what each variable paints.

## What you get

Two blocks of CSS. `:root` holds the light theme's eight brand colors, the corners and the gutters; `.dark` holds the dark theme's eight brand colors. Both are needed: one block alone leaves the other theme with the default brand. For the library's own violet at the default corners, the Theme page writes:

```css
:root {
  --color-brand: rgb(115 71 217);
  --color-brand-hover: rgb(95 53 184);
  --color-brand-fg: rgb(255 255 255);
  --color-brand-solid: rgb(115 71 217);
  --color-brand-solid-fg: rgb(255 255 255);
  --color-brand-subtle: rgb(245 244 255);
  --color-brand-subtle-fg: rgb(95 53 184);
  --color-brand-ring: rgb(191 178 255);
  --radius-control: 8px;
  --radius-badge: 6px;
  --radius-card: 8px;
  --radius-overlay: 12px;
  --space-overlay: 1rem;
  --space-surface: 1rem;
  --space-surface-md: 1.5rem;
  --space-drawer: 1.5rem;
}

.dark {
  --color-brand: rgb(162 135 255);
  --color-brand-hover: rgb(191 178 255);
  --color-brand-fg: rgb(17 24 39);
  --color-brand-solid: rgb(115 71 217);
  --color-brand-solid-fg: rgb(255 255 255);
  --color-brand-subtle: rgb(54 26 113);
  --color-brand-subtle-fg: rgb(215 209 255);
  --color-brand-ring: rgb(95 53 184);
}
```

## Where it goes

In your app's stylesheet, below the imports. The library sets its own values first, and the later rule wins, so a theme above `@import 'grani/style.css'` is overwritten.

```css
/* src/style.css */
@import 'tailwindcss';
@import 'grani/style.css';

/* your theme from the Theme page */
:root {
  --color-brand: …;
}

.dark {
  --color-brand: …;
}
```

## Colors

Every color is a variable: set it in `:root` for the light theme and in `.dark` for the dark one. The defaults, by group:

### Brand

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-brand` | Links, the selected tab, a brand icon and outline | `var(--lavender-600)` | `var(--lavender-400)` |
| `--color-brand-hover` | A link and a brand button under the pointer | `var(--lavender-700)` | `var(--lavender-300)` |
| `--color-brand-fg` | Text on a brand fill: the picked day of a calendar | `rgb(255 255 255)` | `rgb(17 24 39)` |
| `--color-brand-solid` | The primary button | `var(--lavender-600)` | `rgb(130 88 232)` |
| `--color-brand-solid-fg` | The label on the primary button | `rgb(255 255 255)` | `rgb(255 255 255)` |
| `--color-brand-subtle` | A soft brand button, a brand row under the pointer, the span of a calendar | `var(--lavender-50)` | `var(--lavender-900)` |
| `--color-brand-subtle-fg` | Text on brand-subtle | `var(--lavender-700)` | `var(--lavender-200)` |
| `--color-brand-ring` | For the app: a brand outline of its own; no component paints it | `var(--lavender-300)` | `var(--lavender-700)` |

### Surfaces

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-bg` | The page under everything | `rgb(249 250 251)` | `rgb(17 24 39)` |
| `--color-surface` | Cards, windows, drawers, panels | `rgb(255 255 255)` | `rgb(31 41 55)` |
| `--color-surface-muted` | A table head, a muted footer, a hovered row | `rgb(249 250 251)` | `rgb(24 32 46)` |

### Text

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-fg` | Body text and titles | `rgb(17 24 39)` | `rgb(243 244 246)` |
| `--color-fg-muted` | Descriptions, captions, secondary text | `rgb(107 114 128)` | `rgb(156 163 175)` |
| `--color-fg-subtle` | Marks and glyphs that only hint | `rgb(156 163 175)` | `rgb(107 114 128)` |
| `--color-fg-inverse` | Text on a dark plate: a tooltip | `rgb(255 255 255)` | `rgb(17 24 39)` |

### Lines

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-border` | Card edges, dividers, the secondary button | `rgb(229 231 235)` | `rgb(55 65 81)` |
| `--color-border-strong` | Stronger lines: a switch track, a hovered secondary button | `rgb(209 213 219)` | `rgb(75 85 99)` |
| `--color-ring` | The focus halo, at a quarter of its strength | `rgb(156 163 175)` | `rgb(107 114 128)` |

### Controls

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-control-bg` | The inside of a field | `rgb(249 250 251)` | `rgb(55 65 81)` |
| `--color-control-border` | The outline of a field, a checkbox, a radio | `rgb(209 213 219)` | `rgb(75 85 99)` |
| `--color-control-border-hover` | That outline under the pointer | `rgb(156 163 175)` | `rgb(107 114 128)` |
| `--color-disabled-bg` | A disabled field | `rgb(243 244 246)` | `rgb(31 41 55)` |
| `--color-disabled-fg` | The text of a disabled control | `rgb(156 163 175)` | `rgb(107 114 128)` |
| `--color-placeholder` | The hint inside an empty field | `rgb(107 114 128)` | `rgb(166 173 185)` |
| `--color-selected` | A selected row of a table | `var(--lavender-50)` | `var(--lavender-900)` |
| `--color-selected-fg` | Text on a selected row | `var(--lavender-700)` | `var(--lavender-200)` |
| `--color-backdrop` | The dim behind a window and a drawer | `rgb(0 0 0)` | `rgb(0 0 0)` |
| `--color-skeleton` | The bars of a loading skeleton | `rgb(209 213 219)` | `rgb(75 85 99)` |

### Neutral

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-neutral-subtle` | A neutral badge, an empty plate | `rgb(243 244 246)` | `rgb(55 65 81)` |
| `--color-neutral-subtle-fg` | Text on neutral-subtle | `rgb(31 41 55)` | `rgb(209 213 219)` |
| `--color-neutral-border` | The edge of a neutral outline badge | `rgb(229 231 235)` | `rgb(75 85 99)` |

### Success

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-success` | A success fill, icon and text | `var(--success-700)` | `var(--success-700)` |
| `--color-success-fg` | Text on the success fill | `rgb(255 255 255)` | `rgb(255 255 255)` |
| `--color-success-subtle` | The plate of a success badge | `var(--success-100)` | `rgb(21 86 61)` |
| `--color-success-subtle-fg` | Text on that plate | `var(--success-800)` | `rgb(119 223 163)` |
| `--color-success-border` | A success outline | `var(--success-700)` | `var(--success-500)` |

### Danger

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-danger` | A danger fill: Delete, an error icon | `var(--danger-600)` | `var(--danger-600)` |
| `--color-danger-fg` | Text on the danger fill | `rgb(255 255 255)` | `rgb(255 255 255)` |
| `--color-danger-subtle` | The plate of a danger badge | `var(--danger-100)` | `rgb(97 37 51)` |
| `--color-danger-subtle-fg` | Text on that plate, a field error | `var(--danger-700)` | `rgb(250 139 145)` |
| `--color-danger-border` | The outline of a field in error | `var(--danger-600)` | `var(--danger-500)` |

### Warning

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-warning` | A warning fill and icon | `var(--warning-400)` | `var(--warning-400)` |
| `--color-warning-fg` | Text on the warning fill | `var(--warning-950)` | `var(--warning-950)` |
| `--color-warning-text` | Warning text on the page, where the fill would not read | `var(--warning-950)` | `var(--warning-400)` |
| `--color-warning-subtle` | The plate of a warning badge | `var(--warning-100)` | `rgb(100 73 25)` |
| `--color-warning-subtle-fg` | Text on that plate | `var(--warning-950)` | `rgb(255 219 118)` |
| `--color-warning-border` | A warning outline | `var(--warning-700)` | `var(--warning-400)` |

### Info

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-info` | An info fill and icon | `var(--info-600)` | `var(--info-500)` |
| `--color-info-fg` | Text on the info fill | `rgb(255 255 255)` | `rgb(255 255 255)` |
| `--color-info-subtle` | The plate of an info badge or callout | `var(--info-100)` | `rgb(27 51 115)` |
| `--color-info-subtle-fg` | Text on that plate, an info link | `var(--info-700)` | `var(--info-300)` |
| `--color-info-border` | An info outline | `var(--info-600)` | `var(--info-400)` |

### Dots

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-neutral-dot` | The dot of a neutral badge | `rgb(107 114 128)` | `rgb(156 163 175)` |
| `--color-brand-dot` | The dot of a brand badge | `var(--lavender-600)` | `rgb(130 88 232)` |
| `--color-success-dot` | The dot of a success badge | `var(--success-600)` | `var(--success-500)` |
| `--color-warning-dot` | The dot of a warning badge | `var(--warning-500)` | `var(--warning-400)` |
| `--color-danger-dot` | The dot of a danger badge | `var(--danger-600)` | `var(--danger-500)` |
| `--color-info-dot` | The dot of an info badge | `var(--info-500)` | `var(--info-400)` |

### Charts

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-chart-1` | The first series of a chart, the brand | `var(--lavender-600)` | `var(--lavender-400)` |
| `--color-chart-2` | The second series | `var(--amber-600)` | `var(--amber-400)` |
| `--color-chart-3` | The third series | `rgb(2 132 199)` | `rgb(56 189 248)` |
| `--color-chart-4` | The fourth series | `rgb(219 39 119)` | `rgb(244 114 182)` |
| `--color-chart-5` | The fifth series | `rgb(13 148 136)` | `rgb(45 212 191)` |
| `--color-chart-other` | Every series past the fifth, summed as Other | `rgb(107 114 128)` | `rgb(156 163 175)` |

### Accent

| Variable | Paints | Light | Dark |
| --- | --- | --- | --- |
| `--color-accent` | For the app: a second hue of its own; no component paints it | `var(--amber-500)` | `var(--amber-400)` |
| `--color-accent-fg` | Text on accent | `rgb(255 255 255)` | `rgb(255 255 255)` |
| `--color-accent-subtle` | A soft plate of accent | `var(--amber-50)` | `var(--amber-900)` |

## Corners and gutters

Corners and gutters are lengths, one for every theme. `--radius-control: 0` gives a square product, `1rem` a soft one. The Theme page writes the gutters at the library's defaults; change them by hand.

| Variable | Default | Sizes |
| --- | --- | --- |
| `--radius-control` | `0.5rem` | Buttons, fields, selects: every control |
| `--radius-badge` | `0.375rem` | Badges and small marks |
| `--radius-card` | `0.5rem` | Cards and tables |
| `--radius-overlay` | `0.75rem` | Windows, drawers, popovers, menus |
| `--radius-pill` | `9999px` | What is always round: a switch, a count, an avatar |
| `--space-overlay` | `1rem` | The inset of a window and a popover |
| `--space-surface` | `1rem` | The gutter of a card |
| `--space-surface-md` | `1.5rem` | The gutter of a card at `md` |
| `--space-drawer` | `1.5rem` | The gutter of a drawer |

## Your own color

A color of your own is a variable for each theme and a Tailwind color over it. Put it below the imports; `inline` makes the class read the variable, so it changes with the theme.

```css
/* src/style.css, below the imports */
:root {
  --highlight: oklch(0.95 0.06 95);
}
.dark {
  --highlight: oklch(0.42 0.08 95);
}
@theme inline {
  --color-highlight: var(--highlight);
}
```

Then it is a class like any other, in both themes:

```html
<p class="bg-highlight text-fg">Marked for review</p>
```

## The size of controls

The size of every control is not a variable: it is an option of the plugin, in `main.ts`.

```ts
// src/main.ts
createApp(App).use(createGrani({ size: 'md' })).mount('#app')
```

## Dark theme

The dark theme is the `dark` class on `<html>`. Tailwind's own `dark:` follows the system instead; point it at the class, below the imports, so your classes and grani agree.

```css
@custom-variant dark (&:where(.dark, .dark *));
```
