Skip to content

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.

Open Theme

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.

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. Here is where the common ones paint; all of them follow.

  1. --color-surfacethe card
  2. --color-borderits edge, its lines and Cancel
  3. --color-fgthe title, the label, the value
  4. --color-control-bgthe inside of the field
  5. --color-control-borderits outline
  6. --color-success-subtlethe Paid plate
  7. --color-danger-subtlethe Overdue plate
  8. --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.

VariableDefaultSizes
--radius-control0.5remButtons, fields, selects: every control
--radius-badge0.375remBadges and small marks
--radius-card0.5remCards and tables
--radius-overlay0.75remWindows, drawers, popovers, menus
--radius-pill9999pxWhat is always round: a switch, a count, an avatar
--space-overlay1remThe inset of a window and a popover
--space-surface1remThe gutter of a card
--space-surface-md1.5remThe gutter of a card at md
--space-drawer1.5remThe 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.

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.

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