Primitives Forms
Color field
The native color input drawn as a swatch. The browser owns the picker and the value, a lowercase hex string; the package adds a stylesheet and a contract. Tier 0.
- Tier
- 0 · Native
- Behavior JS
- 0 B brotli · 0 B gzip ·
fixtures/· methodresults.json (native-only) - Platform features
<input type="color">,::-webkit-color-swatch,::-moz-color-swatch- Shared listeners
- none
- Per-instance listeners
- none
- Lazy state
- none
- Native base
<input type="color">
On this page
Example
<label for="color-field-accent">Accent color</label>
<input
id="color-field-accent"
type="color"
name="accent"
value="#2f6fde"
data-slean="color-field"
/><script lang="ts">
// Nothing to import: the browser owns the field. The stylesheet is optional.
import '@svelte-lean/styles/color-field.css';
let accent = $state('#2f6fde');
</script>
<label for="color-field-accent">Accent color</label>
<input
id="color-field-accent"
type="color"
name="accent"
bind:value={accent}
data-slean="color-field"
/>Why this implementation exists
A custom color picker is a large piece of interface: a saturation plane, a hue slider, text fields, and keyboard support for each. The platform ships one behind <input type="color">, keyboard-operable, with the value in one fixed format.
Svelte Lean draws the control as a swatch in a bordered box, through the swatch pseudo-elements, and leaves the picker to the browser.
The browser owns
- the color picker (in some browsers the operating system’s color panel)
- the value, always a lowercase #rrggbb string
- Enter and Space to open the picker, and the picker’s own keyboard
- form participation and the label association
Svelte Lean owns
- color-field.css: the bordered box and the swatch, through the prefixed swatch pseudo-elements
- the contract
- documentation
Usage
The markup needs no package. Install @svelte-lean/styles for the stylesheet; @svelte-lean/primitives adds the typed contract and nothing at runtime.
npm install @svelte-lean/stylespnpm add @svelte-lean/stylesyarn add @svelte-lean/stylesbun add @svelte-lean/stylesimport '@svelte-lean/styles/color-field.css';Write value as #rrggbb. The value read back is always lowercase hex, whatever the picker showed.
The swatch shows the color, not its code. When the reader needs the code, bind the value and show it in an <output>.
Anatomy
| Part | Element | data-slean-part | Required | Notes |
|---|---|---|---|---|
| root | <input type="color" data-slean="color-field" value="#2f6fde"> | – | yes | A visible label: the swatch shows the value, not the purpose. The value is #rrggbb. |
| swatch | ::-webkit-color-swatch, ::-moz-color-swatch | – | styles only | The browser’s swatch; the stylesheet rounds it and gives it a border. |
Runtime profile
Tier 0: the field has no behavior module, and the Vite plugin maps color-field to no module. The picker and the value are the browser’s.
Accessibility contract
- A visible label through
<label for>: the swatch shows the value, not the purpose. - One tab stop; Enter or Space opens the picker.
- The picker’s keyboard and its announcements belong to the browser or the operating system.
Keyboard
| Key | When | Result |
|---|---|---|
| Tab/Shift+Tab | anywhere | Moves focus to and from the swatch (native) |
| Enter/Space | focus on the swatch | Opens the picker, which has its own keyboard (native) |
Platform features
| Feature | Baseline | Outside the target |
|---|---|---|
<input type="color"> | Widely available | Not applicable; the picker differs between browsers |
::-webkit-color-swatch, ::-moz-color-swatch | Prefixed, non-standard | The browser’s default swatch inside the styled box |
alpha, colorspace | Safari 27 implements both; Chrome 153 neither | Not used: the value stays #rrggbb in every browser |
Without JavaScript
Fully functional: picking a color and submitting it with a form need no script.
Server rendering
The value attribute renders the initial color.
Before hydration
Nothing is attached; the field works before and after hydration alike.
Styling
color-field.css removes the native appearance, draws a bordered box at the control height and rounds the swatch through ::-webkit-color-swatch and ::-moz-color-swatch, each in its own rule because a selector list with an unknown pseudo-element is dropped whole. Under forced colors the swatch keeps its color, since it is the value. The tokens it reads:
| Token | Default (light) | Applies to |
|---|---|---|
--slean-control-height-md | 2.25rem | inline-size, block-size |
--slean-space-1 | 0.25rem | padding |
--slean-control-border | var(--slean-border-strong) | border |
--slean-radius-md | 0.625rem | border-radius |
--slean-control-bg | var(--slean-surface) | background |
--slean-duration-fast | 100ms | transition |
--slean-ease | cubic-bezier(0.2, 0, 0, 1) | transition |
--slean-border | var(--slean-neutral-6) | border |
--slean-radius-sm | 0.375rem | border-radius |
--slean-control-border-hover | var(--slean-accent) | border-color |
--slean-control-border-focus | var(--slean-accent) | border-color |
--slean-control-ring | 0 0 0 3px color-mix(in oklch, var(--slean-accent) 22%, transparent) | box-shadow |
--slean-warning | oklch(76% 0.16 80) | border-color |
--slean-control-ring-warning | 0 0 0 3px color-mix(in oklch, var(--slean-warning) 28%, transparent) | box-shadow |
--slean-danger | oklch(55% 0.2 25) | border-color |
--slean-control-ring-danger | 0 0 0 3px color-mix(in oklch, var(--slean-danger) 22%, transparent) | box-shadow |
--slean-control-border-disabled | var(--slean-border) | border-color |
--slean-control-bg-disabled | var(--slean-muted) | background-color |
--slean-fg-muted | var(--slean-neutral-11) | color |
State selectors the stylesheet targets, all from the platform or ARIA: ::-moz-color-swatch, ::-webkit-color-swatch, ::-webkit-color-swatch-wrapper, :disabled, :focus-visible, :hover, :user-invalid, [aria-invalid="true"].
Variant attributes: data-status (warning, error).
Controlled integration
Bind the value to show it, store it or apply it. It is always a lowercase #rrggbb string.
<script lang="ts">
let accent = $state('#2f6fde');
</script>
<label for="color-field-accent">Accent color</label>
<input id="color-field-accent" type="color" name="accent" bind:value={accent}
data-slean="color-field" />
<!-- The value is always lowercase #rrggbb. -->
<output for="color-field-accent">{accent}</output>Compatibility notes
The picker differs between browsers. The HTML standard adds alpha and colorspace attributes: Safari 27 implements both, and with either one the value becomes a CSS color function instead of hex; Chrome 153 implements neither. The contract does not use them, so the value stays #rrggbb everywhere.
Testing
apps/playground/tests/primitives/inputs-overlays.spec.tsPlaywright, with page JavaScript enabled and disabled, and axepackages/styles/testsstatic checks of the stylesheet (layers, tokens, specificity, dark parity)
Source
packages/primitives/src/color-field/contract.mdthe contract in prose: semantics, keyboard, focus, ARIA, SSR, JS-disabled behaviorpackages/primitives/src/color-field/contract.tstyped constants: name, tier, base, parts, options, eventspackages/styles/css/color-field.cssthe optional stylesheet