WebCraft

oklch(from …)

Colours that go together, and which of them you can put text on

A wheel tells you what is related. It does not tell you what is legible, which is the half that decides whether a palette survives contact with an interface. Pick one colour: every harmony below is one hue rotation away from it, corrected for the fact that sRGB holds a vivid blue and a vivid yellow-green at very different lightnesses — and every swatch is measured on screen and says to your face whether text can sit on it.

AnalogousNeighbours on the wheel. Quiet, and the safest thing to build a page out of.
ComplementThe opposite hue. One accent against one field — never a 50/50 split, which reads as a clash rather than a pair.
Split complementThe complement's two neighbours instead of the complement itself. Most of the contrast, less of the vibration.
TriadThree hues at even spacing. Give one the room and the other two the accents.
Tints and shadesThe same hue at five lightnesses. This is the row most interfaces are actually built from.
Its greysNeutrals carrying a trace of the hue. Grey mixed from the brand colour sits with it; grey off a swatch card does not.

This browser has no relative colour syntax, so every swatch above is the base colour and nothing else. The rest of the page still works.

CaveatEqual chroma at every hue is a lie sRGB will not honour: there is far less room for a vivid yellow-green at a given lightness than for a blue, so the browser quietly maps the difference away and the wheel's triad comes out as khaki. The rows above correct for it — each rotated hue is given back the lightness its own hue can carry, which is the cos() term in the snippet below — but it is an approximation of the gamut, not a measurement of it. Judge the row on screen, never on the numbers. The contrast figures are read off the rendered swatch for exactly that reason.

FallbackThe picker is <input type="color">, which is sRGB hex and nothing else: you cannot start from a P3 colour here even though oklch() could describe one. Without relative colour syntax every swatch falls back to the base colour, and the page says so above rather than showing a row of identical squares with no explanation.

CSS
.palette { --base: #3d5afe; }

/* a hue rotation, plus the lightness
   sRGB owes you for landing on a
   yellow-green instead of a blue */
.sw[data-dh] {
  background: oklch(
    from var(--base)
    clamp(0.2, calc(l + 0.14 * (
      max(0, cos((h + var(--dh)
                  - 100) * 1deg))
      - max(0, cos((h - 100) * 1deg))
    )), 0.93)
    c calc(h + var(--dh)));
}

/* … or a new lightness outright,
   with the chroma pulled back */
.sw[data-tone] {
  background: oklch(
    from var(--base)
    var(--l) calc(c * var(--cm)) h);
}