oklch-pickerPlaygroundGitHub

Usage

The value semantics follow each framework's idiom, and the emitted value is always a canonical, gamut-clamped oklch(L C H) string. What you pass in is more forgiving: oklch(), rgb(),hsl(), hwb() or hex, with or without alpha.

Drag anything. The field in the footer shows what reaches your onChange.
import { useState } from "react";
import { ColourPicker } from "@oklch-picker/react";
import "@oklch-picker/core/styles.css";

export function Example() {
  const [colour, setColour] = useState("oklch(0.7 0.15 255)");
  return <ColourPicker value={colour} onChange={setColour} />;
}
<script setup>
import { ref } from "vue";
import { ColourPicker } from "@oklch-picker/vue";
import "@oklch-picker/core/styles.css";

const colour = ref("oklch(0.7 0.15 255)");
</script>

<template>
  <ColourPicker v-model="colour" />
</template>
<script>
  import { ColourPicker } from "@oklch-picker/svelte";
  import "@oklch-picker/core/styles.css";

  let colour = $state("oklch(0.7 0.15 255)");
</script>

<ColourPicker bind:value={colour} />
import { createSignal } from "solid-js";
import { ColourPicker } from "@oklch-picker/solid";
import "@oklch-picker/core/styles.css";

export function Example() {
  const [colour, setColour] = createSignal("oklch(0.7 0.15 255)");
  return <ColourPicker value={colour()} onChange={setColour} />;
}
import { Component, signal } from "@angular/core";
import { ColourPickerComponent } from "@oklch-picker/angular";
import "@oklch-picker/core/styles.css";

@Component({
  selector: "app-example",
  standalone: true,
  imports: [ColourPickerComponent],
  template: `
    <oklch-colour-picker
      [value]="colour()"
      (valueChange)="colour.set($event)"
    />
  `,
})
export class Example {
  readonly colour = signal("oklch(0.7 0.15 255)");
}
import { $, component$, useSignal } from "@builder.io/qwik";
import { ColourPicker } from "@oklch-picker/qwik";
import "@oklch-picker/core/styles.css";

export const Example = component$(() => {
  const colour = useSignal("oklch(0.7 0.15 255)");
  return (
    <ColourPicker
      value={colour.value}
      onChange$={$((c: string) => (colour.value = c))}
    />
  );
});
---
import "@oklch-picker/core/styles.css";
---

<oklch-picker value="oklch(0.7 0.15 255)"></oklch-picker>

<script>
  import "oklch-picker/register";
</script>
<link rel="stylesheet" href="https://esm.sh/@oklch-picker/core/styles.min.css" />

<oklch-picker id="picker" value="oklch(0.7 0.15 255)"></oklch-picker>

<script type="module">
  import "https://esm.sh/oklch-picker/register";

  document.getElementById("picker").addEventListener("change", (event) => {
    console.log(event.detail.colour); // "oklch(0.7 0.15 120)"
  });
</script>

The adapters are controlled. A picker keeps no colour of its own: it renders whatever value you pass, so the value it hands back has to come back in. v-model andbind:value do that for you. React, Solid, Angular and Qwik want the pair wired explicitly, and wiring only the callback leaves the sliders stuck while the picker emits against a colour that never changes.

The <oklch-picker> element is the exception. It holds its own colour and updates its own value attribute, so it works with no listener at all.

No framework

oklch-picker is a custom element, so it is just a tag. That covers plain HTML, HTMX, Alpine, Astro, and any server-rendered page: Rails, Laravel, Django, PHP, WordPress. No framework, no bundler, and no build step.

styles.min.css is the same stylesheet at 2.1 kB gzipped instead of 6.1 kB, worth using whenever nothing in front of it will minify. With a bundler, import plain styles.css: your build minifies it anyway, and the readable file is where the --okp-* variables are documented.

With a bundler the import is import "oklch-picker/register". Either way that one side-effect import defines the tag. That is the whole client-side cost, and nothing else needs wiring.

It works in forms

The element is form-associated, so it submits under its namelike a built-in input: no hidden field, and no JavaScript to sync one. A server can render the current value and read the new one straight back from the POST body:

<form method="post">
  <oklch-picker name="brand" value="<%= @brand_colour %>"></oklch-picker>
  <button>Save</button>
</form>

Resetting the form restores the value the server rendered, again like a built-in input.

In Astro

No client:* directive, because there is no framework to hydrate. The page ships the markup and the element upgrades itself. This site is built that way, and every demo here is the custom element.

---
import "@oklch-picker/core/styles.css";
---

<oklch-picker value="oklch(0.7 0.15 255)"></oklch-picker>

<script>
  import "oklch-picker/register";
</script>

Configuring it

value, layout, and class-prefix are plain attributes. parts, labels, andpresets accept JSON attributes too, so no scripting is needed to configure them:

<oklch-picker
  layout="compact"
  presets='["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]'
  parts='{"charts": false}'
  labels='{"l": "Helderheid"}'
></oklch-picker>

presets also takes a plain comma-separated list. From script, all of them are settable as properties (picker.presets = [...]), and picker.value reads and writes the current colour.

The element renders into the light DOM, so the stylesheet and--okp-* overrides apply exactly as they do elsewhere, which also means it is not style-isolated.

Presets

Swatches shown below the sliders. Clicking one commits it, so it also joinsrecent colours.

Five presets, below the sliders.
<ColourPicker
  value={colour}
  onChange={setColour}
  presets={["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]}
/>
<ColourPicker v-model="colour" :presets='["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]' />
<ColourPicker bind:value={colour} presets={["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]} />
<ColourPicker
  value={colour()}
  onChange={setColour}
  presets={["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]}
/>
<oklch-colour-picker
  [value]="colour()"
  (valueChange)="colour.set($event)"
  [presets]="presets"
/>
<ColourPicker
  value={colour.value}
  onChange$={$((c: string) => (colour.value = c))}
  presets={["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]}
/>
<oklch-picker
  presets='["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]'
></oklch-picker>
<oklch-picker
  presets='["oklch(0.75 0.16 145)", "oklch(0.7 0.15 255)"]'
></oklch-picker>