Overview
A text input that validates and normalizes hex, rgb(), hsl(), hwb(), and oklch() color strings into a canonical value emitted on blur — hex by default, or another CSS Color 4 format via format. Compose it with FormField for label and external-error display, and pair it with ColorPicker when users need both visual selection and exact entry.
Usage
value is bindable. Use bind:value when parent state should track the committed canonical value, or pass value with onValueChange when you want to persist commits explicitly.
Behavior
- Validation happens on blur, not on every keystroke, so users can type intermediate values like
#abwithout seeing the error flicker. - The emitted value's syntax is controlled by
format(default'hex';'rgb','hsl','hwb', and'oklch'are also available). Hex output is always lowercase:#rrggbbunlessalpha={true}and the parsed value has partial alpha — then it's#rrggbbaa. Every other format uses modern space-separated CSS Color 4 syntax with slash alpha (e.g.oklch(l c h / a)). Each format decides "opaque" using its own quantization, not literally alpha=== 1: hex omits the alpha byte once it rounds to0xff(any alpha>= ~0.998), while every other format omits/ aonce alpha rounds to1at 4 decimal places (any alpha>= 0.99995). Opaque inputs never gain a spurious alpha suffix in any format. formats(plural) gates accepted input syntax independently offormat(singular, output) — a consumer can acceptoklch()input while still emitting hex, for example. The configuredformatis always an implicit member of the accepted-input set too, regardless of whatformatslists — the field must be able to parse back the exact syntax it just emitted, soformats={['hex']}withformat="rgb"still accepts user-enteredrgb()values. Don't rely onformatsto exclude the configured output format's syntax.- When
alpha={false}(default), alpha-bearing input (#RRGGBBAA,rgba(),hsla(), or a translucentoklch()/hwb()) is parsed but alpha is stripped on emit, uniformly across everyformat. - The trailing color swatch reads
committedHexonly. It never reflects unparsed text, so a malformed string never paints arbitrary content into the DOM. - Pressing Enter commits the value. With
enterBehavior='commit-then-submit'(default), the field then callsform.requestSubmit()on the associated form. WithenterBehavior='commit-only', submission is suppressed.
Value ownership
- Bindable state:
bind:valueupdates only after a successful commit. Intermediate keystrokes stay local so users can type partial values like#abwithout pushing invalid state to the parent. - Explicit commit handling:
onValueChangefires when a successful commit changes the committed value (in the configuredformat's syntax). It is not forwarded to the inner native<input>. - External updates: setting
valuefrom the parent reconciles the visible text. Invalid external strings remain visible and raise the parse error instead of silently clearing.
Form participation
The component renders a single sibling <input type="hidden"> that serves two purposes. When name is set, that input carries the name attribute and mirrors the current committed value (in the configured format's syntax) so it participates in native form submission. When name is not set, the same input still renders (without a name) and acts purely as the anchor used to attach a reset listener to the surrounding form. Either way, uncontrolled fields revert to value on form reset (no onValueChange is fired; reset is observable through native form events). Controlled fields do nothing on reset internally; the parent's reset handler updates value and the effect reconciles.
Parse errors propagate to the visible <input> via setCustomValidity, so invalid text participates in HTML constraint validation whether the user pressed Enter or clicked a submit button.
Moving the component across forms at runtime is not supported in v1.
Limitations
- Intake accepts both legacy comma syntax (
rgb(r, g, b)) and modern space-separated syntax with slash alpha (rgb(r g b / a),hsl(h s l / a)) for every accepted format, including whatever the field's ownformatemits — round-tripping an emitted value back into the field always parses. rgb()percent components are rounded to the nearest 0–255 byte.commit-then-submitselects the first non-disabled[type="submit"](or unmarked<button>) in document order, matching the common case but not every native browser nuance. Forms needing full fidelity should useenterBehavior='commit-only'and orchestraterequestSubmit(submitter)themselves.- The component contributes a form value only when
nameis set — just like a native<input>withoutname. Pressing Enter on a field without anamestill commits and submits, but no color appears inFormData. oninputis not exposed. The component owns the blur-time commit pipeline; intermediate keystrokes are intentionally not surfaced as a value callback.
Errors and accessibility
- Parse errors are owned by
ColorFieldand rendered by the innerInput. The native<input>carriesaria-invalid="true"and anaria-describedbythat references the inline error message. - When wrapped in a
FormFieldwith its ownerror="...", both error texts render and both ids appear inaria-describedbywithout collision — theInputallocates a distinct id when its own error would collide with the context's error id. - The trailing swatch is decorative: it is
aria-hidden="true".
When to use
- Accepting an exact color value via keyboard entry, including pasted hex, rgb(), or hsl() strings.
- Pairing with color-picker for combined visual selection and text-based entry.
- Letting users graze visually across a color space — use color-picker instead.
- Constraining selection to a fixed brand palette — use color-swatch-picker instead.
Examples
Basic color field
An uncontrolled color field that accepts hex, rgb(), and hsl() strings and normalizes them to hex on commit.
Output format
The format prop controls the emitted string syntax (hex, rgb, hsl, hwb, or oklch). The selected output format is always implicitly accepted on input too, regardless of what the formats prop lists.
Disabled and readonly states
A disabled color field rejects all interaction; a readonly field displays the value but blocks editing.
Props
Name | Type | Default | Description |
|---|---|---|---|
id required | text | Inner <input> id. Required (mirrors Input). | |
value bindable | text | '' | Bindable value, committed in the syntax the configured format prop
selects (plain hex by default; modern CSS Color 4 syntax for the other
formats — see format). Accepts any color string the configured
formats allow when set externally, plus whatever syntax format
itself uses (see formats below — the configured output format is
always an implicitly accepted input format too). |
alpha | boolean | false | Accept and emit alpha when the parsed value has partial alpha. When
false (default), alpha-bearing input (#RRGGBBAA, rgba(),
hsla(), or a translucent oklch()/hwb()) is parsed but alpha is
stripped on emit, uniformly across every format — not a hex-only
concern. |
formats | ColorFieldFormat[] | Accepted *input* formats. Defaults to ['hex', 'rgb', 'hsl', 'hwb'];
rgba/hsla aliases can be restricted independently. Add 'oklch' to
accept oklch() input strings. The configured format (below) is
always an implicitly accepted input format too, regardless of what's
listed here — the field must be able to parse back the exact syntax it
just emitted, so e.g. formats={['hex']} with format="rgb" still
accepts user-entered rgb() values. Don't rely on formats to exclude
the configured output format's syntax. This implicit widening admits
ONLY the configured format's own exact syntax, never a legacy alias —
formats={['hex']} with format="rgb" still rejects rgba() input
unless 'rgba' (or 'rgb') is explicitly listed in formats too. | |
format | 'hex''rgb''hsl''hwb''oklch' | 'hex' | Output color format for the committed/emitted value. Default 'hex'.
Purely additive — existing consumers relying on the hex default are
unaffected. value stays a plain string regardless of format. lab is
deliberately excluded. Implicitly widens the accepted *input* set too —
see formats above. |
disabled | boolean | false | Disable the input. |
required | boolean | false | Mark the input as required for form submission and a11y. |
readonly | boolean | false | Render the inner <input> as read-only. |
name | text | Form field name. When set, the hidden mirror input contributes the
current committed value — in the configured format's syntax — to
native form submission. | |
placeholder | text | Placeholder text for the inner <input>. | |
errorMessage | text | Override the default parse-failure error message. | |
enterBehavior | 'commit-then-submit' | 'commit-only' | 'commit-then-submit' | Commit-on-Enter behavior. Default 'commit-then-submit':
- 'commit-then-submit': Enter commits the value, then lets the form's
native submission proceed via requestSubmit.
- 'commit-only': Enter commits and preventDefault()s, suppressing
form submission (useful in dialogs / multi-field flows where Enter
must not submit). |
onValueChange | (value: string) => void | Fires on successful blur-time commit when the committed value — in the
configured format's syntax — actually changes. Value callback by repo
convention — not forwarded to the inner native <input>. |