Theming
Your brand, colors, corners and gutters are CSS variables. The Theme page 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:
: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.
@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. Here is where the common ones paint; all of them follow.
--color-surfacethe card--color-borderits edge, its lines and Cancel--color-fgthe title, the label, the value--color-control-bgthe inside of the field--color-control-borderits outline--color-success-subtlethe Paid plate--color-danger-subtlethe Overdue plate--color-brand-solidSave
Brand
- brandLinks, the selected tab, a brand icon and outline
- brand-hoverA link and a brand button under the pointer
- brand-fgText on a brand fill: the picked day of a calendar
- brand-solidThe primary button
- brand-solid-fgThe label on the primary button
- brand-subtleA soft brand button, a brand row under the pointer, the span of a calendar
- brand-subtle-fgText on brand-subtle
- brand-ringFor the app: a brand outline of its own; no component paints it
Surfaces
- bgThe page under everything
- surfaceCards, windows, drawers, panels
- surface-mutedA table head, a muted footer, a hovered row
Text
- fgBody text and titles
- fg-mutedDescriptions, captions, secondary text
- fg-subtleMarks and glyphs that only hint
- fg-inverseText on a dark plate: a tooltip
Lines
- borderCard edges, dividers, the secondary button
- border-strongStronger lines: a switch track, a hovered secondary button
- ringThe focus halo, at a quarter of its strength
Controls
- control-bgThe inside of a field
- control-borderThe outline of a field, a checkbox, a radio
- control-border-hoverThat outline under the pointer
- disabled-bgA disabled field
- disabled-fgThe text of a disabled control
- placeholderThe hint inside an empty field
- selectedA selected row of a table
- selected-fgText on a selected row
- backdropThe dim behind a window and a drawer
- skeletonThe bars of a loading skeleton
Neutral
- neutral-subtleA neutral badge, an empty plate
- neutral-subtle-fgText on neutral-subtle
- neutral-borderThe edge of a neutral outline badge
Success
- successA success fill, icon and text
- success-fgText on the success fill
- success-subtleThe plate of a success badge
- success-subtle-fgText on that plate
- success-borderA success outline
Danger
- dangerA danger fill: Delete, an error icon
- danger-fgText on the danger fill
- danger-subtleThe plate of a danger badge
- danger-subtle-fgText on that plate, a field error
- danger-borderThe outline of a field in error
Warning
- warningA warning fill and icon
- warning-fgText on the warning fill
- warning-textWarning text on the page, where the fill would not read
- warning-subtleThe plate of a warning badge
- warning-subtle-fgText on that plate
- warning-borderA warning outline
Info
- infoAn info fill and icon
- info-fgText on the info fill
- info-subtleThe plate of an info badge or callout
- info-subtle-fgText on that plate, an info link
- info-borderAn info outline
Dots
- neutral-dotThe dot of a neutral badge
- brand-dotThe dot of a brand badge
- success-dotThe dot of a success badge
- warning-dotThe dot of a warning badge
- danger-dotThe dot of a danger badge
- info-dotThe dot of an info badge
Charts
- chart-1The first series of a chart, the brand
- chart-2The second series
- chart-3The third series
- chart-4The fourth series
- chart-5The fifth series
- chart-otherEvery series past the fifth, summed as Other
Accent
- accentFor the app: a second hue of its own; no component paints it
- accent-fgText on accent
- accent-subtleA soft plate of accent
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.
/* 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:
<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.
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.
@custom-variant dark (&:where(.dark, .dark *));