Examples / Color Picker

Color Picker

An interactive color picker with live preview and hex/RGB display.

ui intermediate ★ Featured
Loading preview…

An interactive color picker with hue, saturation, and lightness sliders. Shows a live preview swatch and the color in hex and RGB formats. Demonstrates multiple signals feeding into a computed value.

import { html, signal, computed } from "https://esm.sh/@elurjs/core@4.0.1";

const hue = signal(220);
const sat = signal(80);
const light = signal(55);

const hsl = computed(() => `hsl(${hue.value}, ${sat.value}%, ${light.value}%)`);
const hex = computed(() => {
  const h = hue.value / 360;
  const s = sat.value / 100;
  const l = light.value / 100;
  const hue2rgb = (p, q, t) => {
    if (t < 0) t += 1;
    if (t > 1) t -= 1;
    if (t < 1/6) return p + (q - p) * 6 * t;
    if (t < 1/2) return q;
    if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
    return p;
  };
  const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  const p = 2 * l - q;
  const r = Math.round(hue2rgb(p, q, h + 1/3) * 255);
  const g = Math.round(hue2rgb(p, q, h) * 255);
  const b = Math.round(hue2rgb(p, q, h - 1/3) * 255);
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
});

const presets = ["#3432c8", "#256fe1", "#2bc7f0", "#34d399", "#fbbf24", "#f87171", "#f472b6"];

const setFromHex = (h) => {
  const r = parseInt(h.slice(1, 3), 16) / 255;
  const g = parseInt(h.slice(3, 5), 16) / 255;
  const b = parseInt(h.slice(5, 7), 16) / 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  const l = (max + min) / 2;
  let s = 0;
  const h_delta = max - min;
  if (h_delta !== 0) {
    s = l > 0.5 ? h_delta / (2 - max - min) : h_delta / (max + min);
  }
  let h_val = 0;
  if (max === r) h_val = ((g - b) / h_delta + (g < b ? 6 : 0)) / 6;
  else if (max === g) h_val = ((b - r) / h_delta + 2) / 6;
  else h_val = ((r - g) / h_delta + 4) / 6;
  hue.value = Math.round(h_val * 360);
  sat.value = Math.round(s * 100);
  light.value = Math.round(l * 100);
};

html`
  <div style="text-align:center">
    <div class="swatch" style=${() => `background:${hsl.value};margin:0 auto 16px`}></div>

    <div style="display:flex;flex-direction:column;gap:12px;width:100%;max-width:300px;margin:0 auto">
      <div>
        <label>Hue: ${() => hue.value}°</label>
        <input type="range" min="0" max="360" value=${() => hue.value}
          aria-label="Hue"
          @input=${(e) => hue.value = parseInt(e.target.value)}
          style="width:100%;accent-color:#2bc7f0" />
      </div>
      <div>
        <label>Saturation: ${() => sat.value}%</label>
        <input type="range" min="0" max="100" value=${() => sat.value}
          aria-label="Saturation"
          @input=${(e) => sat.value = parseInt(e.target.value)}
          style="width:100%;accent-color:#2bc7f0" />
      </div>
      <div>
        <label>Lightness: ${() => light.value}%</label>
        <input type="range" min="0" max="100" value=${() => light.value}
          aria-label="Lightness"
          @input=${(e) => light.value = parseInt(e.target.value)}
          style="width:100%;accent-color:#2bc7f0" />
      </div>
    </div>

    <div style="margin-top:16px">
      <p style="font-family:monospace;font-size:1.1rem">${() => hex.value}</p>
      <p style="font-family:monospace;color:#a0a0b5">${() => hsl.value}</p>
    </div>

    <div style="display:flex;gap:8px;justify-content:center;margin-top:12px;flex-wrap:wrap">
      ${presets.map((c) => html`
        <button
          aria-label=${`Select color ${c}`}
          style=${() => `width:32px;height:32px;border-radius:8px;background:${c};border:2px solid ${hex.value === c.toUpperCase() ? "#2bc7f0" : "#2a2a3a"};cursor:pointer`}
          @click=${() => setFromHex(c)}
        ></button>
      `)}
    </div>
  </div>
`.mount("#app");