Monsoon v0.2.0
Made by Alexandre Kohler Star

Getting started

Theming with one seed color

A Monsoon theme is one line of CSS: --seed: #0d9488. The browser derives primary, secondary and accent colors, surfaces, borders, muted text and the focus ring from it, with foregrounds that keep readable contrast. No preprocessor and no generator website.

5 min read

Set the seed

Any valid CSS color works: hex, a named color, rgb(), hsl() or oklch(). Set it on the root element to theme the whole page:

theme.css
:root {
  --seed: #0d9488;
}

Load this after Monsoon, in your own stylesheet or in a <style> tag placed after the link, so it wins over the default. Without it, the default seed is oklch(0.55 0.22 262), a saturated blue. To experiment, add style="--seed: ..." to an element in the playground and watch the tokens change.

Every derived token

Each token is a custom property (var(--primary)) and a family of utilities (bg-primary, text-primary, border-primary, ring-primary and more). Lightness values are on the OKLCH scale from 0 (black) to 1 (white):

TokenLight / dark lightnessWhat it is for
background0.99 / 0.16The page background. Chroma is capped near zero, so only a hint of the seed hue remains.
foreground0.21 / 0.96Body text on background and the surfaces.
surface1 / 0.2Cards and panels.
surface-2, surface-30.97, 0.94 / 0.24, 0.28Raised or nested layers: table headers, wells, hover fills.
muted0.955 / 0.27Subtle fills such as tags and code backgrounds.
muted-foreground0.47 / 0.74Secondary text, captions and placeholders.
border0.9 / 0.33Hairlines. It is also the default color of every border, so border alone needs no color class.
primarythe seed, moved out of 0.52 to 0.66Main actions and links. In dark mode the seed is lifted to at least 0.7 first.
primary-foreground1 or 0.17Text on primary: white on a dark primary, near black on a light one.
ringsame as primaryFocus rings, the default ring color and the focus outline in the reset.
secondary0.94 / 0.3Quiet buttons and chips: a low chroma tint of the seed.
accent0.74 to 0.88, both modesHighlights: the seed hue turned by 180 degrees, kept light.
success, warning, danger, infofixed huesStatus colors at hues 150, 80, 27 and 235. They do not follow the seed.

primary, secondary, accent and the four status colors each have a -foreground partner for text on top of them, muted pairs with muted-foreground, and foreground is the text color for background and the surfaces. The 22 hue palette (bg-emerald-100, text-rose-600) is separate and fixed: it never follows the seed.

Tokens from the default seed
primary
secondary
accent
muted
surface
surface-3
success
danger

Theme one section

Custom properties are computed where they are declared, so the tokens must be declared again wherever the seed changes. Monsoon does that automatically for any element whose style attribute sets --seed:

section.html
<section style="--seed: #db2777" class="rounded-2xl bg-surface p-6 space-y-3">
  <h2 class="text-xl font-bold text-primary">Spring sale</h2>
  <button class="rounded-lg bg-primary text-primary-foreground px-4 py-2">Shop now</button>
</section>
Three seed islands on one page

#0891b2

secondary

#db2777

secondary

#facc15

secondary

Look at the yellow island: a light seed gets near black text on its primary button, while the darker seeds get white text. Each island also tints its own surface and computes its own accent.

Setting the seed from your own CSS

If you set --seed from your stylesheet instead of the style attribute, add the theme class to the same element. It tells Monsoon to recompute the tokens there without painting anything:

brands.css
[data-brand="ocean"] {
  --seed: #0369a1;
}
plans.html
<div data-brand="ocean" class="theme rounded-2xl border bg-surface p-6">
  <a href="/plans" class="text-primary font-medium">See plans</a>
</div>

Override a single token

Sometimes the brand needs one exact color. Set that token yourself after Monsoon loads. Use the same list of selectors Monsoon uses for its tokens, so the override also applies inside themed sections:

theme.css
:root, .theme, .dark, .light, [data-theme], [style*="--seed"] {
  --danger: #c81e1e;
}

The partner token follows automatically: --danger-foreground is computed from var(--danger), so it still flips between white and near black for your color. The same is true for primary, secondary, accent and the status colors.

To give an override a different value in dark mode, use --mode, which is 0 in light mode and 1 in dark mode, the same way Monsoon does:

theme.css
:root, .theme, .dark, .light, [data-theme], [style*="--seed"] {
  /* lightness 0.5 in light mode, 0.72 in dark mode */
  --info: oklch(calc(0.5 + 0.22 * var(--mode)) 0.13 250);
}

An overridden color skips the lightness adjustment that Monsoon applies to the seed. Check its contrast against its foreground in both modes.

Accent: the opposite hue

The accent token takes the seed hue and turns it by 180 degrees, so it always sits on the opposite side of the color wheel: a teal seed gets a coral accent, a blue seed gets an amber one. Its lightness is kept between 0.74 and 0.88 and its chroma is capped at 0.16, so it reads as a bright highlight in both modes, always with dark text.

Use it sparingly, for things that should stand out from the primary color: a "new" badge, a highlighted price, the second stop of a gradient.

banner.html
<div class="h-24 rounded-2xl bg-gradient-to-br from-primary to-accent"></div>
Primary to accent for three seeds

Contrast guarantees

White text needs a dark fill and near black text needs a light fill. Around an OKLCH lightness of 0.52 to 0.66, neither choice reaches 4.5:1 for every hue, so Monsoon moves the primary color out of that band, to the side that is closer to your seed, and flips its foreground on the same threshold. The other foregrounds flip on the same rule.

The test suite checks a contrast ratio of at least 4.5:1 (WCAG AA for body text) for these 13 pairs, in light and dark mode, for 18 named seeds and a sweep of 2,197 seeds across sRGB:

  • primary, secondary, accent, success, warning, danger and info, each with its -foreground,
  • foreground on background, surface, surface-2 and surface-3,
  • muted-foreground on background and on muted.

The guarantee covers those pairs. Combinations you choose yourself, such as palette colors or text-accent on a white surface, are up to you to check.

Frequently asked questions

Which color formats can I use for --seed?

Any CSS color: hex, named colors, rgb(), hsl(), oklch() and others. The browser converts it to OKLCH for the calculations.

Can different parts of a page use different seeds?

Yes. Put style="--seed: ..." on any element, or set the seed with a class and add theme to that element.

Do the palette colors change with the seed?

No. The 22 hue palette is fixed. Only the semantic tokens follow the seed.

What happens in browsers without relative colors?

They get static tokens computed from the default seed, with primary and ring set to your seed directly. See Browser support.

No recent searches

Try , , or .

Recent

Loading the search index

No results for ""

Check the spelling, or search a class name such as px-4.