Skip to content

API Reference

The <color-input> element accepts these HTML attributes:

The value attribute sets the color using standard CSS color syntax.

<color-input value="oklch(70% 20% 240)"></color-input>
Value
oklch(70% 20% 240)
Color Space
oklch
Gamut
—
Contrast Color
—
View Code
<color-input value="oklch(70% 20% 240)"></color-input>

Accepted inputs include common CSS formats such as:

  • hex colors
  • rgb() / hsl() / hwb()
  • lab() / lch() / oklab() / oklch()
  • color(display-p3 ...), color(rec2020 ...), color(prophoto-rgb ...), and similar color(...) forms
  • CSS named colors such as rebeccapurple

Without an explicit colorspace or initial-colorspace, the editing space is inferred from the string’s notation, since the notation you wrote is the one you probably want to keep editing in. #3b82f6 opens in hex, hsl(240 80% 60%) in hsl, color(display-p3 ...) in display-p3.

Named colors are the exception. rebeccapurple expresses no format preference, so it opens in oklch rather than srgb, which puts the wide-gamut controls in front of you by default.

A parseable color whose space has no controls in the picker also falls back to oklch.

The colorspace attribute controls which editing space the UI uses.

<color-input
value="color(display-p3 0.3 0.7 0.95)"
colorspace="oklch"
></color-input>

When this changes, the component converts the current color into the requested space and rebuilds the controls to match.

Use initial-colorspace when you want the picker to open in a specific editing space without rewriting the incoming value string on first render.

<color-input value="#000" initial-colorspace="hsl"></color-input>

If colorspace is also present, colorspace wins.

Start from a hex token but open the picker with HSL controls for hue and saturation editing.

Value
#3b82f6
Color Space
oklch
Gamut
—
Contrast Color
—
View Code
<color-input value="#3b82f6" initial-colorspace="hsl"></color-input>

The theme attribute controls the visual appearance:

  • auto (default) - Respects user’s system preference
  • light - Always uses light theme
  • dark - Always uses dark theme
Value
oklch(65% 20% 280)
Color Space
oklch
Gamut
—
Contrast Color
—
View Code
<color-input value="oklch(65% 20% 280)"></color-input>

The no-alpha attribute hides the alpha channel control from the color picker. When present, users cannot modify the alpha/opacity value through the UI, though the alpha value is still preserved in the color.

<color-input value="red" no-alpha></color-input>
Value
red
Color Space
oklch
Gamut
—
Contrast Color
—
View Code
<color-input value="red" no-alpha></color-input>

The no-gamut-boundaries attribute hides the gamut boundary guide lines from the 2D area picker while leaving the underlying color editing behavior unchanged.

<color-input value="color(display-p3 0.3 0.7 0.95)" no-gamut-boundaries></color-input>
Value
color(display-p3 0.3 0.7 0.95)
Color Space
oklch
Gamut
—
Contrast Color
—

The form field name. <color-input> is a form-associated custom element, so with a name its current value is submitted with the form and shows up in FormData, just like <input>.

<form>
<color-input name="brand" value="#3b82f6"></color-input>
</form>

Disables the trigger and inline field, keeps the picker from opening, and leaves the value out of form submission. Inline pickers become inert. An ancestor <fieldset disabled> has the same effect, and either way the host matches :disabled.

<color-input name="brand" disabled></color-input>

<color-input> participates in forms the way <input type="color"> does:

  • Submission and FormData: a named element contributes its current value, kept in sync on every edit, drag, or paste.
  • Labels: <label for="id"> and wrapping <label> both work. Clicking the label opens the picker (or focuses an inline one), and the label text names the trigger button and inline field for screen readers. An aria-label on the host does the same.
  • Reset: a form reset restores the color and editing space the element was connected with, and fires change. Set defaultValue to choose a different reset target. (The value attribute can’t double as the default the way it does on <input>, because the component reflects every edit into it.)
  • Disabled: the element’s own disabled attribute or an ancestor <fieldset disabled>.
  • History: back/forward navigation and reloads restore the color and editing space, without firing change.
<form>
<label for="brand">Brand color</label>
<color-input id="brand" name="brand" value="#3b82f6"></color-input>
<label>
Accent
<color-input name="accent" value="oklch(70% 0.2 140)"></color-input>
</label>
<button type="reset">Reset</button>
<button>Save</button>
</form>
const form = document.querySelector("form");
const brand = document.querySelector("#brand");
new FormData(form).get("brand") === brand.value; // true
brand.form === form; // true
brand.labels[0].textContent; // "Brand color"

There are two editable text fields for the color string: the inline field next to the trigger, and the field in the picker’s preview footer. Both accept anything the CSS Color Level 4 parser understands and keep the sliders, area picker, preview, and computed properties in sync as you type.

  • Validation runs on every keystroke, and a valid value commits immediately. The sliders rebuild when the entered syntax implies a different colorspace.
  • Leaving a field reverts any uncommitted text — an unparseable entry, or one that normalized to a different string — back to the current value.
  • Pasted text is normalized before parsing: surrounding whitespace and zero-width characters are dropped, a wrapping CSS declaration is unwrapped (color: red; → red), and bare hex digits gain a leading # (ff6600 → #ff6600).
  • When parsing fails the host gains a data-error attribute, the textbox sets aria-invalid="true", and the inline error message becomes visible.
  • Restyle the field with ::part(input) and target invalid states with [data-error].
<color-input value="#9333ea"></color-input>

The value shown over the color swatch is editable too, which makes it a one-click paste target while the picker is open.

  • Focusing it selects the whole value, so a paste replaces it and a manual copy needs no dragging.
  • Invalid entries mark the field aria-invalid="true" and draw an outline in the swatch’s contrast color. They don’t set the host-level data-error state.
  • Enter commits and leaves the field. Escape abandons the edit — including any color already committed live — and keeps the picker open; a second Escape closes it.
  • Restyle it with ::part(output).

Hover or focus the preview area to reveal a copy button. Activating it copies the current color string with navigator.clipboard.writeText() and shows a “Copied!” status message inside the panel. No configuration is required—the control works for every supported color space, including hex.

Hover or focus the preview area to reveal an eye dropper button to the left of the copy button. Activating it opens the native browser eye dropper, allowing you to pick colors from anywhere on your screen.

The browser returns an sRGB hex value, which the component then converts into the current editing space before updating value.

The <color-input> element exposes these properties:

value - Current color as a CSS string

const picker = document.querySelector("color-input");
console.log(picker.value); // "oklch(70% 20% 240)"
picker.value = "oklch(80% 15% 180)";

colorspace - Current color space

console.log(picker.colorspace); // "oklch"
picker.colorspace = "hsl";

Supported values:

  • hex
  • srgb
  • srgb-linear
  • hsl
  • hwb
  • lab
  • lch
  • oklab
  • oklch
  • display-p3
  • a98-rgb
  • rec2020
  • prophoto
  • xyz
  • xyz-d50
  • xyz-d65

initialColorspace - Preferred startup color space

console.log(picker.initialColorspace); // null | "hsl" | "oklch" | ...
picker.initialColorspace = "hsl";

theme - Visual theme

console.log(picker.theme); // "auto"
picker.theme = "dark"; // "auto", "light", or "dark"

name / disabled - Reflect the name and disabled attributes

defaultValue - The color a form reset restores (defaults to the value the element was connected with)

picker.defaultValue = "oklch(70% 20% 240)";
picker.form.reset(); // picker.value === "oklch(70% 20% 240)"

noAlpha - Controls alpha channel visibility

console.log(picker.noAlpha); // false
picker.noAlpha = true; // Hides alpha controls
picker.noAlpha = false; // Shows alpha controls

showGamutBoundaries - Controls gamut boundary guide visibility

console.log(picker.showGamutBoundaries); // true
picker.showGamutBoundaries = false; // Hides gamut boundary lines
picker.showGamutBoundaries = true; // Shows gamut boundary lines

gamut - Detected gamut (smallest gamut that contains the current color)

console.log(picker.gamut); // "srgb" | "p3" | "rec2020" | "xyz"

contrastColor - Recommended text color for contrast

console.log(picker.contrastColor); // "white" or "black"

form / labels / validity / validationMessage / willValidate - The standard form-control properties, backed by ElementInternals. checkValidity() and reportValidity() are available too.

Opens the picker popover programmatically:

const picker = document.querySelector("color-input");
picker.show();

show() can also accept a custom anchor element:

picker.show(document.querySelector("#anchor"));

Alias for show() retained for compatibility with earlier versions:

picker.showPicker();

Closes the picker popover:

picker.close();

Sets a custom anchor element for popover positioning:

const customAnchor = document.querySelector("#my-anchor");
picker.setAnchor(customAnchor);

By default, the popover positions relative to the internal trigger button. When possible, the component can use native CSS anchor positioning; otherwise it falls back to manual viewport-aware placement.

<color-input> listens for Popover command events so you can wire it up with declarative popovertarget controls or custom command dispatchers.

  • Buttons with popovertarget automatically send show, hide, or toggle commands that the element consumes.
  • The component accepts either the short (show, hide, toggle) or Popover-specific (show-popover, hide-popover, toggle-popover) command strings.
  • When a command originates from an invoker element, that invoker becomes the new popover anchor via setAnchor().
<button popovertarget="picker" popovertargetaction="toggle">
Toggle color picker
</button>
<color-input id="picker"></color-input>

You can also trigger commands manually:

const cmd = new Event("command", { bubbles: true });
Object.assign(cmd, { command: "show" });
picker.dispatchEvent(cmd);

The <color-input> element dispatches three custom events:

Fired when the color value changes. The event detail contains:

interface ChangeDetail {
value: string; // CSS color string, e.g., "oklch(70% 20% 240)"
colorspace: string; // Current color space, e.g., "oklch"
gamut: string; // Detected gamut: "srgb", "p3", "rec2020", or "xyz"
}
picker.addEventListener("change", (event) => {
const { value, colorspace, gamut } = event.detail;
console.log(`New color: ${value}`);
console.log(`Color space: ${colorspace}`);
console.log(`Gamut: ${gamut}`);
});

Fired when the picker popover opens:

picker.addEventListener("open", () => {
console.log("Picker opened");
});

Fired when the picker popover closes:

picker.addEventListener("close", () => {
console.log("Picker closed");
});

The popover includes a two-dimensional color area. Its model changes with the active colorspace:

  • srgb and hex use an OKHSV-based plane
  • hsl uses saturation × lightness
  • oklch, lch, and wide-gamut RGB-like spaces use a chroma × lightness plane
  • oklab and lab use a centered a × b plane
  • hwb uses whiteness × blackness

Click or drag anywhere inside the area to update the current color. The thumb tracks the current position and the serialized value updates as you move.

Focus the area and use the arrow keys:

  • ←/→ — adjust the X-axis channel in fine steps
  • ↑/↓ — adjust the Y-axis channel in fine steps

Target the area container with ::part(area):

color-input::part(area) {
border-radius: 12px;
aspect-ratio: 16 / 9;
}

For spaces with explicit gamut behavior, the area can render boundary overlays and stretch the mapping so the useful region of the gamut occupies more of the canvas.

The <color-input> element exposes several CSS parts for styling with the ::part() selector:

  • trigger - The button that opens the picker
  • chip - Color swatch inside the trigger button
  • input - Inline text field for direct color entry
  • error - Text with color parsing error message
  • panel - Popover container
  • output - Editable CSS color string in the preview footer
  • gamut - Gamut badge (srgb/p3/rec2020/xyz)
  • controls - Container for channel sliders and inputs
  • area - Container for area picker

The preview area also exposes two useful pieces of information:

  • a gamut indicator showing the smallest tracked gamut containing the current color
  • contrast ratios against white and black

These are especially useful when the picker is part of a design tool, theming UI, or token editor.

While the picker is open, the color string is mirrored into a polite live region so changes made through the sliders, area picker, or eye dropper are announced. Only the settled value is announced — a slider drag doesn’t produce an announcement per frame — and typing into either text field is skipped, since the field already echoes what you entered.