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
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.
/* 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.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.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.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 withpreload: falseso other palettes don’t download it. - Shape.
shadow-pop,shadow-control,lift,press,clip-control,notch,display-shadow,highlight,heading-transformandtiltset how panels and buttons carry themselves. The built-in palettes use them heavily; seesrc/styles/themes. - Glass. Keep
color-surfacesee-through, at about 78%, and setglass-edge,glass-sheenandglass-shadowto suit. For a flat, printed look, make the edge your border colour and turn the shadow off withtheme.$no-shadow, as Pop does. - Cursor.
cursor-effectpicks what CursorEffects draws, andcursor-customandcursor-custom-pointertake any CSScursorvalue, such as an SVG made withpatterns.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.
@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),