Backgrounds✨ Premium

Relative Color Palette

A palette of ten shades cascades from a single CSS custom property — OKLCH lightness steps and color harmonies recompute instantly on every hue change.

CSSJSOKLCH

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Backgrounds has 50 effects, including 6 free. Explore the category →

3 usage examples

Brand palette explorer — design system documentation — Relative Color Palette example 1

① Brand palette explorer — design system documentation

WhenA brand guidelines or design system page presents the product's color spectrum and lets the team explore hue variants.
WhyThe widget surfaces all ten derived shades — lightness steps and harmonies — in a single glance. Developers immediately identify the tokens to implement and grasp the chromatic relationships visually.
Settings--base-hue: 250 for brand blue-indigo, swatches scaled up to 48 × 48 px in CSS, container at padding: 28px for breathing room.
Settings panel — accent color picker — Relative Color Palette example 2

② Settings panel — accent color picker

WhenA SaaS lets users customize their workspace accent color from the account settings.
WhyThe widget collapses an entire palette into a single click — users instantly see how their chosen hue scales across light and dark tones without opening a native color picker. The harmony row helps validate chromatic consistency before applying.
Settings--base-hue: 160 (emerald green default), swatches at 32 × 32 px to fit inside a settings card, background: transparent on the container.
Product color configurator — e-commerce product card — Relative Color Palette example 3

③ Product color configurator — e-commerce product card

WhenA customer selects a color for a garment or accessory on the product page: the card background, promo badge, color swatches, and purchase button all re-derive from the same OKLCH hue.
WhyThe shopper immediately sees how their color choice impacts the entire product presentation — background, badge, button — with zero visual inconsistency. The effect proves that a single <code>--base-hue</code> is enough to coordinate a whole non-technical interface, with no extra JavaScript.
Settings--base-hue: 340 (rose-red for apparel), color swatches at 20 × 20 px rounded for a compact picker, card background at oklch(.09 .04 var(--base-hue)) for a subtle hue tint.

How it works

The effect relies entirely on the --base-hue custom property set on the [data-relcolor] container. All descendants inherit this value via var(--base-hue) inside their oklch() declarations. The first row produces five lightness variants at progressively reduced chroma : oklch(.85 .15 H) (lightest), .75 .20, .60 .25 (base), .45 .20, .30 .15 (darkest). Because OKLCH is perceptually uniform, every lightness step looks visually equivalent regardless of hue — something HSL cannot guarantee.

The second row exposes five chromatic harmonies computed via calc() on the hue : complement at H + 180, analogous at H ± 30, triadic at H + 120 and H + 240. The CSS engine resolves these expressions natively — zero JavaScript computation. Harmony chroma is slightly reduced (0.22 vs 0.25 for the base) so that color-relationship swatches stay distinct without becoming aggressively saturated.

The JavaScript is intentionally minimal : querySelectorAll("[data-relcolor]") initializes each instance with t = 250 (blue-indigo) and sets --base-hue via element.style.setProperty(). On click, t = (t + 47) % 360 advances the hue by 47° — a prime number that visits widely separated angles before cycling back to near-neighbors. Multiple instances on the same page work without conflict because each forEach iteration closes over its own t.

The swatch hover effect (transform: scale(1.2) translateY(-4px), 0.4 s transition) is the component's only animation. Without JavaScript, the palette remains visible thanks to CSS-computed oklch() values — only the click-to-rotate behavior is inactive.

Accessibility

  • prefers-reduced-motion: not implemented. The only animation is the hover scale(1.2) on swatches (0.4 s CSS transition) — a light, user-triggered motion. If this matters in your context, add @media (prefers-reduced-motion: reduce) { .relcolor-swatch { transition: none } }.
  • The [data-relcolor] container carries no role or aria-label — it is treated as decorative. If the component plays a functional role (theme selection), add role="group" and aria-label="Base color picker".
  • Keyboard: the container is not focusable (no tabindex), so hue rotation via click is not keyboard-accessible. For full access, wrap the trigger in a native <button>.
  • Contrast: OKLCH perceptual uniformity ensures lightness steps are visually distinct from one another, but the text-to-swatch contrast ratio is not computed dynamically — verify it against your target hue before using the component as a functional selector.
  • HTML title attributes on each swatch ("Lighter", "Light", "Base"…) provide minimal hover labels accessible to assistive technologies.

Browser compatibility

Requires the CSS oklch() function and calc() on custom properties — Baseline 2023, available in all modern browsers since mid-2023.

Chrome 111+✓ Full
Firefox 113+✓ Full
Safari 15.4+✓ Full
Edge 111+✓ Full
Mobile iOS 15.4+✓ Full
Android Chrome✓ Full

On older browsers without <code>oklch()</code> support, swatches display a transparent background — no JS errors, layout stays intact. Add a <code>background: #888</code> declaration before each OKLCH rule as a visible fallback for legacy UAs.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="relcolor-container" data-relcolor="" style="--base-hue: 250">
  <div class="relcolor-base-row">
    <div class="relcolor-base"></div>
    <span class="relcolor-base-label">Base</span>
  </div>
  <div class="relcolor-palette">
    <div class="relcolor-swatch sw-lighter" title="Lighter"></div>
    <div class="relcolor-swatch sw-light" title="Light"></div>
    <div class="relcolor-swatch sw-base" title="Base"></div>
    <div class="relcolor-swatch sw-dark" title="Dark"></div>
    <div class="relcolor-swatch sw-darker" title="Darker"></div>
  </div>
  <div class="relcolor-palette">
    <div class="relcolor-swatch sw-complement" title="Complement"></div>
    <div class="relcolor-swatch sw-analogous1" title="Analogous +30"></div>
    <div class="relcolor-swatch sw-analogous2" title="Analogous -30"></div>
    <div class="relcolor-swatch sw-triadic1" title="Triadic +120"></div>
    <div class="relcolor-swatch sw-triadic2" title="Triadic +240"></div>
  </div>
  <span class="relcolor-label">cliquez pour changer la couleur de base</span>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
--base-hue (inline style) 250 Starting OKLCH hue (0–359). 0 = red, 120 = green, 180 = cyan, 250 = blue-indigo, 300 = violet. Override inline or via JS: el.style.setProperty('--base-hue', 160).
t + 47 (JS, line 3) 47 Hue step per click. 47 = prime number that avoids near-neighbors. Try 30 (even twelfths) or 60 (six evenly spaced colors — full wheel in 6 clicks).
.sw-base { background: oklch(.6 .25 …) } L=0.6, C=0.25 Lightness and chroma of the reference swatch. Raise C to 0.37 (sRGB max) for more saturation, lower L to 0.5 for a darker base.
.sw-lighter { background: oklch(.85 .15 …) } L=0.85, C=0.15 Lightest variant. Keep C below the base value to stay in the sRGB gamut at high lightness. Adjust L between 0.78 and 0.92 for the desired brightness.
.relcolor-swatch { width; height; border-radius } 36px, 36px, 8px Swatch dimensions and shape. Scale up to 48 × 48 px for documentation display, or down to 24 × 24 px for a compact toolbar picker.
.relcolor-base { width; height; border-radius } 50px, 50px, 12px Size of the base swatch. Increase to 64 × 64 px for a hero display of the reference color.
.relcolor-container { gap; padding; background } 16px, 20px, #0f0f1a Row spacing, internal padding, and widget background. Set background: transparent to embed inside an existing card.

FAQ

Not natively — the effect expects an OKLCH hue angle (0–359) in --base-hue. In JavaScript, convert your hex to OKLCH with a library like culori: const h = oklch(parse('#3b82f6')).h, then inject the result before the script runs via el.style.setProperty('--base-hue', h).
Add two <div class="relcolor-swatch"> elements to the second row with dedicated CSS classes: .sw-split1 { background: oklch(.58 .21 calc(var(--base-hue) + 150)) } and .sw-split2 { background: oklch(.58 .21 calc(var(--base-hue) + 210)) }. No JavaScript changes are needed — new swatches automatically inherit --base-hue.
HSL is not perceptually uniform: a yellow at hsl(60 100% 50%) appears far brighter than a blue at hsl(240 100% 50%) at the same lightness value. OKLCH guarantees that oklch(.6 .25 60) and oklch(.6 .25 240) have the same perceived luminance — derived palettes are visually consistent regardless of the chosen hue angle.