Palette
Colour theme

Theming

Every colour, font, radius, shadow and motion is a --gm-* token. Change a few with CSS, re-theme the library with SCSS, or add whole palettes alongside the built-in ones.

Try it live

The theme editor changes tokens across this site as you go, and exports what you made as a palette.

1. Override a few tokens with CSS

The library’s tokens live in a cascade layer, so any ordinary rule beats them, whatever order the stylesheets load in. Fine for a handful of changes. The tokens page lists them all.

globals.csscss
/* app/globals.css, imported after "gui-mellow/styles.scss" */
:root {
  --gm-color-primary: #7c3aed;
  --gm-radius-md: 4px;
}

/* Per scheme, if a colour should differ in the dark */
[data-theme="dark"] { --gm-color-primary: #a78bfa; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --gm-color-primary: #a78bfa; }
}

2. Re-theme the defaults with SCSS

Load the global stylesheet through Sass and pass maps of your values. They’re merged over the defaults, so name only what changes. $light and $dark hold the colours and anything else that differs by scheme; $tokens holds the rest.

app/theme.scssscss
// app/theme.scss, imported in place of "gui-mellow/styles.scss"
@use "gui-mellow/styles/global" with (
  $light: ("color-primary": #7c3aed, "color-accent": #db2777),
  $dark: ("color-primary": #a78bfa, "color-accent": #f472b6),
  $tokens: (
    "font-display": (var(--font-display, "Inter"), sans-serif),
    "radius-md": 4px,
  ),
);

3. Add a palette

A palette is a complete alternative theme, switched with data-palette on <html> or any element, with light and dark variants of its own. theme.palette() takes the same three maps. Anything you leave out falls back to the default theme, not to whichever palette was active.

app/themes/ocean.scssscss
// app/themes/ocean.scss
@use "sass:map";
@use "gui-mellow/styles/patterns";
@use "gui-mellow/styles/theme";

@layer gm.tokens, gm.base;

@layer gm.tokens {
  @include theme.palette(
    "ocean",
    $light: (
      "color-bg": #f2f8fb,
      "color-primary": #0e7490,
      "color-accent": #f97316,
      "pattern-color": rgba(14, 116, 144, 0.12),
    ),
    $dark: (
      "color-bg": #04161d,
      "color-primary": #22d3ee,
      "color-on-primary": #04161d,
    ),
    $tokens: (
      "radius-lg": 20px,
      "shadow-pop": 0 5px 0 var(--gm-color-border-strong),
      "lift": translateY(-3px),
      "pattern": map.get(patterns.$hex, image),
      "pattern-size": map.get(patterns.$hex, size),
      "cursor-effect": ripple,
    )
  );
}

Import it after the global stylesheet. Then turn it on for good, or let visitors pick it: ThemeScript applies their choice before the page paints.

app/layout.tsxtsx
// app/layout.tsx
import "gui-mellow/styles.scss";
import "./themes/ocean.scss";

// Always on:
<html lang="en" data-palette="ocean">

// Or let visitors choose:
<PaletteToggle
  palettes={[
    { value: "default", label: "Classic" },
    { value: "ocean", label: "Ocean" },
  ]}
/>

4. Finishing touches

  • Fonts. Give the font tokens a next/font variable with a fallback by name, e.g. (var(--font-ocean, "Outfit"), sans-serif), and load the font with preload: false so other palettes don’t download it.
  • Shape. shadow-pop, shadow-control, lift, press, clip-control, notch, display-shadow, highlight, heading-transform and tilt set how panels and buttons carry themselves. The built-in palettes use them heavily; see src/styles/themes.
  • Glass. Keep color-surface see-through, at about 78%, and set glass-edge, glass-sheen and glass-shadow to suit. For a flat, printed look, make the edge your border colour and turn the shadow off with theme.$no-shadow, as Pop does.
  • Cursor. cursor-effect picks what CursorEffects draws, and cursor-custom and cursor-custom-pointer take any CSS cursor value, such as an SVG made with patterns.svg-url().
  • Pattern. Use a built-in one, or draw your own tile. It’s used as a mask, so only its shape matters: draw in black.
A pattern of your ownscss
@use "gui-mellow/styles/patterns";

$waves: (
  image: patterns.svg-url(
    "<svg xmlns='http://www.w3.org/2000/svg' width='40' height='20'>" +
      "<path d='M0 10q10-8 20 0t20 0' fill='none' stroke='black'/></svg>"
  ),
  size: 40px 20px,
);

// …then in the palette's $tokens:
"pattern": map.get($waves, image),
"pattern-size": map.get($waves, size),