GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 Color Picker Design Patterns (Copy-Paste)

Nine color-picker UIs that compute: a point's angle turns into a hue, swatch brightness flips the check-icon color, and distance between recent colors

Auto-plays · click a tile to jump to its section · all nine in one zip

Each of the nine screens below reproduces a color-picker convention as a working computation — "iOS-style wheel," "Material-style slider," and the other descriptive names in this post refer to the familiar layout convention each demo imitates, not to any platform's actual picker. None of the nine carries a listener that responds to real user input (0 of 9) — every one runs purely on requestAnimationFrame cycling on a fixed 2000-3000ms period, replaying pre-set waypoints and formulas so nothing is random and every playback reproduces the exact same scene. There is no browser EyeDropper API anywhere (item 08 samples canvas pixels with getImageData instead), no real image, and no real key or server — every color is a placeholder HSL value computed directly in code.

Anatomy of a color-picker UI — what nine screens compute

None of these nine screens parks a fixed color on the page and leaves it there. The same handful of arithmetic moves — an angle, a brightness score, a distance — keeps resurfacing under a different name, and once that is visible, all nine read as one idea wearing nine costumes.

Why the wheel's hue comes from an angle instead of a lookup table

Item 01 recomputes a point's angle in radians through Math.atan2 and turns that value directly into a hue, so the color under the handle is a consequence of geometry rather than a value someone typed in — a wheel whose hue was stored per position would drift the moment the trajectory changed.

Why brightness keeps deciding borders and icons across three screens

Items 04, 07, and 08 all run the same weighted-channel luma (r*299+g*587+b*114)/1000 and let the result pick a white or black ring, a dark or bright check icon, and a thin or thick border — one threshold trick, three different surfaces.

Why one distance formula shrinks the recent-colors list

Item 09 computes the Euclidean distance between RGB values in the list and merges anything closer than MERGE_DIST 30 into a single chip, so the count line reads 10 → 7 without anyone deciding the number by hand.

Code per screen

Each fence below is pasted directly from that screen's own index.html, and the zip's matching prompt fragment reconstructs the same file when fed to an AI coding tool.

01iOS wheel — a circular color picker that recomputes hue from a point's angle

A point's angle on the color wheel is recomputed in radians and turned directly into a hue value, while its distance from the center becomes the saturation at the same time; it suits mobile app color pickers and photo-editing tools.

iOS wheel — a circular color picker that recomputes hue from a point's angle
Open in a new tab

01 · iOS wheel — a circular color picker that recomputes hue from a point's angle — Measured area 8.494% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

Math.atan2pointAtaria-valuetext

What the numbers say

Cumulative area is 8.494% (max frame 3.083%, mean intensity 88.7) over all 23 of 23 frames — the handle is a small dot on a big wheel, so the footprint stays modest while the hue underneath it swings through the full circle.

  function apply(t) {
    var p = pointAt(t);
    var angle = Math.atan2(p.y, p.x);
    var hue = ((angle * 180 / Math.PI) + 360) % 360;
    var dist = Math.min(1, Math.hypot(p.x, p.y) / RADIUS);
    var sat = dist * 100;
    var rgb = hslToRgb(hue, sat, LIGHTNESS);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
    var ink = brightness > 150;

    handleEl.style.transform = 'translate(-50%, -50%) translate(' + p.x.toFixed(2) + 'px, ' + p.y.toFixed(2) + 'px)';
    handleEl.style.background = hex;
    readoutEl.style.background = hex;
    readoutEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;
    wheelEl.setAttribute('aria-valuenow', Math.round(hue));
    wheelEl.setAttribute('aria-valuetext', hex);
  }

The handle rides a six-waypoint trajectory whose radius keeps changing (this is what separates it from a plain rotation), RADIUS is 60, LIGHTNESS 55, and the whole cycle lasts CYCLE_MS 2000ms.

02Material slider — a color picker that composes a preview color from three handle positions

The position ratio of each of the hue, saturation, and lightness handles is recomputed into three separate values, and those three combine to recalculate the preview swatch's color every time; it suits design tool color panels and theme customizers.

Material slider — a color picker that composes a preview color from three handle positions
Open in a new tab

02 · Material slider — a color picker that composes a preview color from three handle positions — Measured area 17.368% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

valuesAtsetThumbaria-valuenow

What the numbers say

Cumulative area is the widest of the nine at 17.368% (max frame 13.353%, mean intensity 72.3) over all 23 of 23 frames — three thumbs and a preview swatch all repaint at once, so the measured footprint is the biggest in the set.

  function valuesAt(t) {
    var p = t / CYCLE_MS;
    var h, s, l;
    if (p < 1 / 3) {
      var local = p / (1 / 3);
      h = local * 360; s = 60; l = 50;
    } else if (p < 2 / 3) {
      var local2 = (p - 1 / 3) / (1 / 3);
      h = 360; s = 20 + local2 * 70; l = 50;
    } else {
      var local3 = (p - 2 / 3) / (1 / 3);
      h = 360; s = 90; l = 20 + local3 * 60;
    }
    return { h: h % 360, s: s, l: l };
  }

  function setThumb(thumbEl, fillEl, ratio) {
    var x = ratio * TRACK_W;
    thumbEl.style.transform = 'translate(calc(-50% + ' + x.toFixed(2) + 'px), -50%)';
    fillEl.style.transform = 'scaleX(' + ratio.toFixed(4) + ')';
  }

The three-segment valuesAt gives each third of the 2000ms cycle to one handle — hue first, then saturation, then lightness — and setThumb drives the track fill's scaleX from the same ratio so thumb and fill can never disagree.

03Windows spectrum — a color picker that splits a 2D position into saturation and lightness

A point's horizontal and vertical position ratio inside the square field is recomputed into separate saturation and lightness values, then combined with the hue bar's value to recalculate the final color every time; it suits graphics editor color dialogs and brand color assignment screens.

Windows spectrum — a color picker that splits a 2D position into saturation and lightness
Open in a new tab

03 · Windows spectrum — a color picker that splits a 2D position into saturation and lightness — Measured area 15.937% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

hsvToRgbpointAtaria-valuetext

What the numbers say

Mean intensity is the lowest of the nine at 38.1, with cumulative area 15.937% (max frame 9.836%) over all 23 of 23 frames — the marker crosses a wide 140×140 square slowly, so the per-frame pixel change is gentle rather than flashy.

  function apply(t) {
    var p = (t / CYCLE_MS) % 1;
    var pt = pointAt(t);
    var hue = p * 360;
    var sat = pt.x * 100;
    var val = (1 - pt.y) * 100;

    var rgb = hsvToRgb(hue, sat, val);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
    var ink = brightness > 150;

    squareEl.style.setProperty('--hue', hue.toFixed(1));
    squareMarkerEl.style.transform = 'translate(-50%, -50%) translate(' + (pt.x * SQUARE_W).toFixed(2) + 'px, ' + (pt.y * SQUARE_H).toFixed(2) + 'px)';
    hueMarkerEl.style.transform = 'translate(-50%, -50%) translateY(' + ((hue / 360) * STRIP_H).toFixed(2) + 'px)';

    swatchEl.style.background = hex;
    readoutEl.style.background = hex;
    readoutEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;

    squareEl.setAttribute('aria-valuenow', String(Math.round(val)));
    squareEl.setAttribute('aria-valuetext', hex);
    hueStripEl.setAttribute('aria-valuenow', String(Math.round(hue)));
  }

Horizontal position becomes saturation, inverted vertical position becomes value, and the separate hue marker slides down its strip in step — CYCLE_MS is 2400ms here.

04macOS crayon — a grid color picker that sets its border color from a brightness value

The brightness value of the currently selected crayon color is computed, and the selection ring switches automatically to a white or black border depending on whether that value is low or high; it suits retro-style color palettes and casual app theme pickers.

macOS crayon — a grid color picker that sets its border color from a brightness value
Open in a new tab

04 · macOS crayon — a grid color picker that sets its border color from a brightness value — Measured area 14.251% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

role=radiogroupLUM_THRESHOLDcenterOf

What the numbers say

Cumulative area is 14.251% (max frame 2.598%, mean intensity 56.6) over all 23 of 23 frames — the ring glides continuously between crayon centers, so almost every frame moves even though the border flips only at the threshold.

  function apply(t) {
    var idx = Math.min(ORDER.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var a = centerOf(ORDER[idx]);
    var b = centerOf(ORDER[idx + 1]);
    var x = a.x + (b.x - a.x) * frac;
    var y = a.y + (b.y - a.y) * frac;
    ringEl.style.transform = 'translate(' + x.toFixed(2) + 'px, ' + y.toFixed(2) + 'px)';

    var selIndex = ORDER[frac < .5 ? idx : idx + 1];
    var c = CRAYONS[selIndex];
    var rgb = hslToRgb(c.h, c.s, c.l);
    var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 / 255;
    var white = brightness < LUM_THRESHOLD;
    ringEl.classList.toggle('is-white-ring', white);
    ringEl.classList.toggle('is-black-ring', !white);

    var hex = crayonEls[selIndex].dataset.hex;
    swatchEl.style.background = hex;
    hexEl.textContent = hex;

    for (var i = 0; i < crayonEls.length; i++) {
      crayonEls[i].setAttribute('aria-checked', i === selIndex ? 'true' : 'false');
    }
  }

The luma lands on a 0-1 scale after the extra /255, LUM_THRESHOLD is .6 in this zip, and ORDER walks all nine crayons and back within CYCLE_MS 2700ms.

05Figma panel — a color picker that keeps three notations recomputed in sync

A single color value gets recalculated into HEX, RGB, and HSL notation at once, keeping all three fields pointed at exactly the same color; it suits design-collaboration tool color panels and design-token editors.

Figma panel — a color picker that keeps three notations recomputed in sync
Open in a new tab

05 · Figma panel — a color picker that keeps three notations recomputed in sync — Measured area 10.797% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

hexFieldrgbFieldPRESETS

What the numbers say

Cumulative area is 10.797% (max frame 10.31%, mean intensity 42.9) over all 23 of 23 frames — the swatch is the biggest single change surface, which suits a panel whose whole point is notation rather than travel.

  function apply(t) {
    var idx = Math.min(PRESETS.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var a = PRESETS[idx];
    var b = PRESETS[idx + 1];
    var h = a.h + (b.h - a.h) * frac;
    var s = a.s + (b.s - a.s) * frac;
    var l = a.l + (b.l - a.l) * frac;
    var hMod = h % 360;

    var rgb = hslToRgb(h, s, l);
    var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
    var hslStr = 'hsl(' + hMod.toFixed(0) + 'deg ' + s.toFixed(0) + '% ' + l.toFixed(0) + '%)';
    var rgbStr = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';

    swatchEl.style.background = hslStr;
    hexField.textContent = hex;
    rgbField.textContent = rgbStr;
    hslField.textContent = hslStr;
  }

Interpolation happens in HSL space across the 7 PRESETS entries (first and last are the same color at h 10 and h 370, so the loop is seamless), and all three fields are written from that one interpolation — nothing can drift because nothing is stored twice.

06HSL sliders — a color picker that redraws track gradients whenever the hue value changes

Every time the hue slider's value changes, the background gradient of the saturation and lightness slider tracks gets recalculated and redrawn so only the color range matching the current hue shows; it suits precision color-tuning panels and brand palette editing tools.

HSL sliders — a color picker that redraws track gradients whenever the hue value changes
Open in a new tab

06 · HSL sliders — a color picker that redraws track gradients whenever the hue value changes — Measured area 15.53% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

hslbuildGradientGRADIENT_STEPS

What the numbers say

Cumulative area is 15.53% (max frame 10.27%, mean intensity 44.5) over all 23 of 23 frames — two full-width tracks repainting their gradients every frame keeps the footprint high even though each stop is a small change.

  var GRADIENT_STEPS = 20;

  function buildGradient(stopFn) {
    var stops = [];
    for (var i = 0; i < GRADIENT_STEPS; i++) {
      var ratio = i / (GRADIENT_STEPS - 1);
      stops.push(stopFn(ratio) + ' ' + (ratio * 100).toFixed(1) + '%');
    }
    return 'linear-gradient(to right, ' + stops.join(', ') + ')';
  }

  function setThumb(thumbEl, ratio) {
    var x = ratio * TRACK_W;
    thumbEl.style.transform = 'translate(calc(-50% + ' + x.toFixed(2) + 'px), -50%)';
  }

The saturation track's stops fix lightness at 50% and sweep saturation, the lightness track's stops hold the current saturation and sweep lightness, and the cycle (CYCLE_MS 3000ms) drives hue as (p*360)%360 with sine waves shaping the other two sliders.

07Swatch chips — a color picker that matches check-icon contrast to each swatch's brightness

The brightness value of each swatch chip's color is computed, and contrast is recalculated each time so a bright color gets a dark check icon while a dark color gets a bright one; it suits quick color-selection palettes and label and tag color pickers.

Swatch chips — a color picker that matches check-icon contrast to each swatch's brightness
Open in a new tab

07 · Swatch chips — a color picker that matches check-icon contrast to each swatch's brightness — Measured area 9.408% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · See the password

role=radiogrouparia-checkedSWATCHES

What the numbers say

Mean intensity is the highest of the nine at 116.1, with cumulative area 9.408% (max frame 4.298%) over 7 of 23 frames — chips switch discretely every 400ms (SEG_MS from the 2400ms cycle divided across the seven-entry ORDER), so the frames between switches hold still by design rather than by defect.

  function apply(t) {
    var idx = Math.min(ORDER.length - 2, Math.floor(t / SEG_MS));
    var frac = (t % SEG_MS) / SEG_MS;
    var selIndex = ORDER[frac < .5 ? idx : idx + 1];

    for (var i = 0; i < chipEls.length; i++) {
      var isSel = i === selIndex;
      var rgb = HEX[i].rgb;
      var brightness = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
      var ink = brightness > 150;
      chipEls[i].classList.toggle('is-selected', isSel);
      chipEls[i].classList.toggle('is-ink', ink);
      chipEls[i].setAttribute('aria-checked', isSel ? 'true' : 'false');
    }

    hexEl.textContent = HEX[selIndex].hex;
    readoutEl.style.background = chipEls[selIndex].style.background;
    var selBrightness = (HEX[selIndex].rgb[0] * 299 + HEX[selIndex].rgb[1] * 587 + HEX[selIndex].rgb[2] * 114) / 1000;
    readoutEl.classList.toggle('is-ink', selBrightness > 150);
  }

The six SWATCHES span lightness 22% to 78%, so the check icon flips between dark and bright several times in one lap — the HEX lookup is precomputed once at startup, and only the classes and attributes change per frame.

08Eyedropper — a color picker that recomputes its preview from the extracted color's brightness

The brightness value of the color extracted from the screen is computed, and that value jointly resets both the preview swatch's text color and its border thickness; it suits screen color-extraction tools and design-QA color checks.

Eyedropper — a color picker that recomputes its preview from the extracted color's brightness
Open in a new tab

08 · Eyedropper — a color picker that recomputes its preview from the extracted color's brightness — Measured area 8.365% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

getImageDataBANDSaria-hidden

What the numbers say

Cumulative area is the smallest of the nine at 8.365% (max frame 4.43%, mean intensity 98.7) over all 23 of 23 frames — the loupe and preview are both small, but each band crossing swaps the preview color hard, which is why intensity still ranks second.

  function apply(t) {
    var p = (t / CYCLE_MS) % 1;
    var triangle = p < .5 ? p * 2 : (1 - p) * 2;
    var x = 8 + triangle * (CANVAS_W - 16);
    var y = CANVAS_H / 2 + Math.sin(p * Math.PI * 4) * (CANVAS_H / 2 - 6);
    var xi = Math.max(0, Math.min(CANVAS_W - 1, Math.round(x)));
    var yi = Math.max(0, Math.min(CANVAS_H - 1, Math.round(y)));

    var pixel = ctx.getImageData(xi, yi, 1, 1).data;
    var r = pixel[0], g = pixel[1], b = pixel[2];
    var hex = '#' + toHex(r) + toHex(g) + toHex(b);
    var brightness = (r * 299 + g * 587 + b * 114) / 1000;
    var ink = brightness > 150;
    var borderPx = MIN_BORDER + (brightness / 255) * (MAX_BORDER - MIN_BORDER);

    loupeEl.style.transform = 'translate(-50%, -50%) translate(' + x.toFixed(2) + 'px, ' + y.toFixed(2) + 'px)';
    previewEl.style.background = hex;
    previewEl.style.borderWidth = borderPx.toFixed(1) + 'px';
    previewEl.classList.toggle('is-ink', ink);
    hexEl.textContent = hex;
  }

This demo reproduces the eyedropper convention as canvas sampling alone: the cursor rides a triangle path across four BANDS of flat HSL color painted onto a 160×90 canvas, getImageData reads the actual pixel at each rounded position, and the brightness maps onto a border between MIN_BORDER 1 and MAX_BORDER 6 pixels; the browser's EyeDropper API is nowhere in the file.

09Recent colors — a list picker that merges near-duplicate colors by computed distance

The distance between color values in the recent-colors list gets computed, and colors close enough by that distance are merged into one, shrinking how many chips show at once; it suits recent-color shortcuts for repeat tasks and document-editor color history.

Recent colors — a list picker that merges near-duplicate colors by computed distance
Open in a new tab

09 · Recent colors — a list picker that merges near-duplicate colors by computed distance — Measured area 13.808% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

distMERGE_DISThslToRgb

What the numbers say

Cumulative area is 13.808% (max frame 4.094%, mean intensity 45.6) over all 23 of 23 frames — ten chips moving at once keeps the footprint high, while the merge itself is a smooth in-and-back-out pulse within CYCLE_MS 2800ms.

  function dist(a, b) {
    return Math.sqrt(
      Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)
    );
  }

  var RGB = RAW.map(function (c) { return hslToRgb(c.h, c.s, c.l); });

  var kept = [];
  var groupOf = new Array(RAW.length);
  for (var i = 0; i < RAW.length; i++) {
    var found = -1;
    for (var j = 0; j < kept.length; j++) {
      if (dist(RGB[i], RGB[kept[j]]) < MERGE_DIST) { found = kept[j]; break; }
    }
    if (found === -1) {
      kept.push(i);
      groupOf[i] = i;
    } else {
      groupOf[i] = found;
    }
  }

The grouping runs once at startup in RGB space against MERGE_DIST 30: each of the 10 RAW colors either starts a new group or joins the first kept color within 30 units, duplicates then animate toward their group's cell while the count line rewrites itself — the number on screen is a result, never a setting.

Where this actually gets used — a brand palette editor and a product option screen

Someone building a design-system editor usually reaches for a color picker when a fixed brand palette already exists and now needs to be exposed for tweaking. Whether that editor looks more like item 01's ring or item 05's side-by-side panel of three formats depends on whether the person turning the dial is a designer or a developer.

A team building a product options screen runs into a different problem. Item 09's recent-colors list is meant to let someone reuse a color they just picked, but keeping every barely-different pick separate makes that list grow without bound — deciding where the merge distance sits is the real design question behind that one screen.

Both situations usually call for one or two of these computations lifted out on their own rather than the full set of nine wired in at once. Sorting out which of angle, brightness, or distance a given screen actually needs is the first step either way.

Where values reshape the screen — angle, brightness, distance

None of these nine screens holds a fixed visual waiting for a real user — the same value a real picker would produce is exactly what reshapes each screen here too.

Why the wheel never shows a color its geometry does not support

Because item 01 derives hue from atan2 and saturation from hypot(x,y)/RADIUS in the same breath, the readout can only ever show a color the handle's actual position implies — there is no stored color to fall back on, and clamping dist at 1 keeps the outer edge of the wheel from producing an impossible saturation over 100%.

Why three screens can share one brightness formula and still look unrelated

Items 04, 07, and 08 feed the same luma into a border flip, an icon flip, and a border thickness, so the shared arithmetic hides behind three different surfaces — this is the layer a color picker design system actually standardizes, not the widget shapes on top of it.

Why the recent-colors count can say 6 without anyone choosing 6

Item 09's 10 → 7 line is read straight off kept.length after the distance pass, so the merged count would change by itself if a single raw color moved — the count is a measurement of the palette, which is why a figma-style token panel benefits from the same pass.

Three variants, and what changes under reduced motion

Variant What changes What it trades off
Crayon brightness threshold Item 04's LUM_THRESHOLD from .5 to .6 At .5 the border flips often near mid-brightness colors and looks jittery; .6 shifts the flip point toward brighter colors, so the white ring stays on longer across the darker color group
Recent-colors merge distance Item 09's MERGE_DIST from 10 to 30 At 10, barely different colors all stay separate and the list grows long; 30 merges similar colors well and shortens the list, but risks lumping genuinely different colors into one
HSL slider gradient steps Item 06's GRADIENT_STEPS from 6 to 20 6 stops look stepped and blocky but cost less to redraw; 20 makes the transition smooth but adds more values to recompute every time the hue changes

This zip ships the .6, 30, and 20 versions.

What reduced motion turns off, item by item

prefers-reduced-motion: reduce removes travel, not arithmetic, across all nine screens: each file checks window.matchMedia('(prefers-reduced-motion: reduce)') once, calls apply(0) a single time, and skips only the repeating loop — so the angle-to-hue conversion, the brightness-to-contrast flips, and the distance-to-merge grouping all still run and still paint their result. A color picker design css implementation that only animates its contrast without computing it would have nothing left under this setting; these nine do.

Where extreme colors and repeated picks break the math

Three spots in this set show what the recompute step does when the input value sits right at a boundary instead of comfortably inside one.

Why near-black and near-white readouts strain their own ink rule

The brightness > 150 ink flip that items 01, 03, 07, and 08 rely on is a two-way split, so a color sitting almost exactly at the boundary (a mid-gray around #969696) makes text and border hover between states — fine for a demo, worth a wider hysteresis gap in a production picker.

Why picking the same color twice still shows two chips at merge distance 10

At the 10-unit end of item 09's variant range, two visually identical picks whose HSL-to-RGB rounding differs by a few units stay separate because the distance never reaches the threshold — raising MERGE_DIST to 30 is what pulls near-identical repeats together, at the cost of merging genuinely different neighbors.

Why zero alpha is outside what these nine compute

Every screen here computes fully opaque colors — hsl() with no alpha channel anywhere — so a picker that must show a transparency slider needs its own fourth track and a checkerboard backing, which is exactly the boundary this set stops at. The code for all nine screens, including the merge pass and the threshold branches, sits behind 59xbym8t in the zip linked from this post.

AI prompt for these screens

The zip carries one prompt fragment per screen, spelling out that screen's constants and the single value it recomputes, ready to hand to a coding assistant.

What to build: an eyedropper-style color picker that rides a fixed cursor
trajectory across four flat-color bands painted on a 160x90 canvas, reads
the actual pixel at each position with getImageData, computes the pixel's
brightness, and uses that value to jointly reset the preview swatch's text
color and border thickness. No EyeDropper API, no real image, no real key
or server anywhere.

Feeding that block to a coding assistant reproduces item 08's sampling loop — same BANDS, same MIN_BORDER and MAX_BORDER — without anyone retyping a line, and all nine prompt fragments sit in the zip lined up one-to-one against their index.html files.

Accessibility across all nine

Every screen keeps its ARIA state in step with the computed color: item 01 writes the hue into aria-valuenow and the hex into aria-valuetext on the wheel, item 02 rounds all three handle values into their sliders' aria-valuenow, and items 04 and 07 toggle aria-checked on each radio as the selection moves — a screen reader lands on the same value a sighted visitor sees, not a stale one. Browsing a color picker design codepen collection can't show this part: what a screen keeps recomputing after the first paint, not what the first paint looks like.

Questions before you wire up a picker

Are these color picker design patterns free to use in a commercial project?

Yes — every one of the nine screens is plain HTML, CSS, and vanilla JS with zero third-party code, so there is no license or attribution to track down before shipping it in client work.

Why does the swatch-chip item move in only 7 of 23 frames?

Item 07's ORDER holds each of its seven entries for SEG_MS 400ms, so the capture's frame deltas fall between discrete chip switches — the held frames are the picker sitting on one selection, which is the correct behavior for a chip list rather than a tracking error.

Do these demos use the browser's EyeDropper API?

No — item 08 paints four flat-color bands onto its own canvas and reads the pixel under its cursor with getImageData, which reproduces the sampling behavior without depending on the EyeDropper API's availability in any particular browser.

Sources: MDN — prefers-reduced-motion · MDN — getImageData · MDN — hsl() color value · MDN — ARIA attributes

Where this set differs from a parts-bin post that assembles one custom picker from nine JS building blocks is the axis: that post builds a single working picker from parts, while this one recomputes nine platform-style picker conventions as standalone screens. The distance and threshold math here also runs in the opposite direction in a collection of copy-paste date range picker UI patterns, where what gets recomputed is a date selection rather than a color, and the three-handle ratio composition of item 02 shares its arithmetic with nine physics-grade range slider designs built for value selection instead of color.

Enter the archive password

The password is inside this article. You will find it as you read.