GitHub

Components / Form & Input / Color Picker

Color Picker beta

A curated palette first, any color second — swatches for the 95% case, a native picker for the rest.

Preview
Selected —
<div class="k-cp" data-cp data-check="<svg width=&quot;15&quot; height=&quot;15&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;><path d=&quot;m4.5 12.5 5 5L19.5 6.5&quot;/></svg>" style="max-width:300px">
  <div class="k-cp-grid" role="radiogroup" aria-label="Brand color">
    <button type="button" role="radio" aria-checked="true" aria-label="Teal" class="k-cp-sw sel" style="background:hsl(175 84% 32%)"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4.5 12.5 5 5L19.5 6.5"/></svg></button><button type="button" role="radio" aria-checked="false" aria-label="Indigo" class="k-cp-sw" style="background:hsl(239 84% 67%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Violet" class="k-cp-sw" style="background:hsl(262 83% 58%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Rose" class="k-cp-sw" style="background:hsl(347 77% 50%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Amber" class="k-cp-sw" style="background:hsl(38 92% 50%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Green" class="k-cp-sw" style="background:hsl(142 71% 38%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Sky" class="k-cp-sw" style="background:hsl(199 89% 44%)"></button><button type="button" role="radio" aria-checked="false" aria-label="Ink" class="k-cp-sw" style="background:hsl(240 6% 12%)"></button>
    <label class="k-cp-custom" title="Custom color"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg><input type="color" data-cp-custom aria-label="Custom color"></label>
  </div>
  <div class="k-cp-out"><span class="chipc" data-cp-chip style="background:hsl(175 84% 32%)"></span>Selected <code data-cp-hex>—</code></div>
</div>
.k-cp{font-family:var(--k-font);text-align:left}
.k-cp-grid{display:flex;flex-wrap:wrap;gap:8px}
.k-cp-sw{width:34px;height:34px;border-radius:10px;border:1px solid var(--k-line);cursor:pointer;padding:0;display:grid;place-items:center;color:transparent;transition:transform .12s var(--k-ease)}
.k-cp-sw:hover{transform:scale(1.08)}
.k-cp-sw.sel{color:#fff;box-shadow:0 0 0 2px var(--k-bg),0 0 0 4px var(--k-accent)}
.k-cp-custom{position:relative;width:34px;height:34px;border-radius:10px;border:1.5px dashed var(--k-border-strong);display:grid;place-items:center;color:var(--k-ink-3);cursor:pointer;background:var(--k-bg)}
.k-cp-custom input{position:absolute;inset:0;opacity:0;cursor:pointer}
.k-cp-out{display:flex;align-items:center;gap:9px;margin-top:13px;font-size:12.5px;color:var(--k-ink-2)}
.k-cp-out .chipc{width:18px;height:18px;border-radius:6px;border:1px solid var(--k-line)}
.k-cp-out code{font-family:var(--k-mono);font-size:12px;color:var(--k-ink)}
const cp = document.querySelector('[data-cp]');
const cpOut = cp.querySelector('[data-cp-hex]');
const cpChip = cp.querySelector('[data-cp-chip]');
function cpHex(rgb) {
  const m = rgb.match(/\d+/g).map(Number);
  return '#' + m.slice(0, 3).map(n => n.toString(16).padStart(2, '0')).join('').toUpperCase();
}
function cpSelect(el, color) {
  cp.querySelectorAll('.k-cp-sw').forEach(s => { s.classList.remove('sel'); s.setAttribute('aria-checked', 'false'); s.innerHTML = ''; });
  if (el) { el.classList.add('sel'); el.setAttribute('aria-checked', 'true'); el.innerHTML = cp.dataset.check; }
  cpOut.textContent = color;
  cpChip.style.background = color;
}
cp.querySelectorAll('.k-cp-sw').forEach(sw => {
  sw.addEventListener('click', () => cpSelect(sw, cpHex(getComputedStyle(sw).backgroundColor)));
});
const cpCustom = cp.querySelector('[data-cp-custom]');
cpCustom.addEventListener('input', e => cpSelect(null, e.target.value.toUpperCase()));
const cpFirst = cp.querySelector('.k-cp-sw.sel');
if (cpFirst) cpSelect(cpFirst, cpHex(getComputedStyle(cpFirst).backgroundColor));

Usage

For brand colors, labels and categories — anywhere the right answer is usually one of a few known colors. Free-form color fields invite off-brand chaos; the palette is the point.

Accessibility

Swatches are radio inputs in disguise: one selected, arrow keys move, the choice is announced. The custom tile is a native color input, so the full picker is the platform one.

Related parts