Skip to main content
WireKit
Copy for LLM

Color Picker

The <x-wirekit::color-picker> component wraps the native <input type="color"> in a circular swatch with a live hex value readout. The browser provides its OS-level color dialog on click.

Usage

Basic Color Picker

By default the component wraps the native <input type="color"> — the OS color dialog, byte-for-byte the same as previous versions.

Popover Mode

Add popover for a custom, design-system-consistent picker that opens in a panel: a saturation/value plane, a hue slider, an alpha slider, a format-aware text field (cycle HEX → RGB → HSL → OKLCH), a screen eyedropper (where supported), copy-to-clipboard, your preset swatches, and a recent-colors history (localStorage). It's a from-scratch HSV picker — no third-party library. The outer prop surface (name / value / disabled / form binding) is identical to native mode, so flipping popover on never breaks form submission.

The OKLCH format is WireKit's own color space — every --color-wk-* design token is an oklch() value — so set format="oklch" (or cycle to it) when you want output you can paste straight into a theme.

Popover picker with alpha + presets

Hide sections you don't need:

Compact popover — no alpha, no recents

Native picker on touch devices

On phones and tablets you may prefer the platform's own color dialog — the same sheet the default (non-popover) variant opens — over the custom panel. Opt in with native-on-mobile: on touch-primary devices (CSS pointer: coarse) the swatch then opens the native dialog, while desktop pointers keep the full popover:

<x-wirekit::color-picker name="accent" popover native-on-mobile value="#3b82f6" />

The native sheet picks opaque sRGB hex — the alpha slider, presets, format cycling, and eyedropper remain popover-only conveniences, so leave the flag off when those matter on mobile too.

Clear to "no color"

Add with-clear for a clear button in the panel that empties the bound value — useful when "no color selected" is a valid state in your form. Clearing sets the form field to an empty string and the swatch shows a "No color" state until the next pick. Because the native <input type="color"> can't represent an empty value, the clear button is popover only.

Popover picker with a clear button

Custom trigger

In popover mode a trigger slot replaces the default swatch with your own control — a labeled pill, a larger swatch, an icon. WireKit keeps the open toggle, the positioning anchor, and the aria-haspopup="dialog" wiring; the slot supplies the visible content, and its text/icon becomes the trigger's accessible name (so label an icon-only trigger yourself). Provide non-interactive content (text, icon, a styled span) — it renders inside WireKit's trigger button.

Custom trigger

Size Variants

Three Sizes

With Accessible Label

Pass a hidden accessible name via the default slot:

<x-wirekit::color-picker name="brand" value="#ab35ff">
    Brand color
</x-wirekit::color-picker>

The slot content is rendered with sr-only so sighted users see only the swatch, but assistive tech reads the label.

Livewire Integration

<x-wirekit::color-picker> keeps its current color in internal state seeded from the value prop, so when binding with Livewire pass the bound property as :value alongside wire:model to seed the initial color:

<x-wirekit::color-picker wire:model.live="brandColor" :value="$brandColor" />

Without :value the picker starts at #000000 until the user picks a color; wire:model keeps it in sync afterward. This is the framework-agnostic seeding pattern WireKit's stateful controls share — it works in plain Blade forms too.

Optimistic UI

Pass the name of the Livewire method the picker should call, and the color is sent the moment it is settled:

<x-wirekit::color-picker
    name="brand"
    popover
    :value="$brand"
    optimistic="saveBrandColor"
/>

Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}"></script>

Try it

The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic color — one picker per answer the server can give

Accepted

The outline clears and nothing is said.

Refused

Your color stays, and you are told it did not save — the previous one is a click away in recents.

Slow to answer

The outline stays until the answer arrives.

The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo methods so the page can show a real round trip. The block under it is what you write.

"Settled" means the gesture finished, never a timer. Dragging across the saturation plane sends nothing while the pointer is down; the color goes out when you release it. A swatch, a recent, the eyedropper, an arrow-key nudge and a typed value each settle in one step and send immediately.

A refusal keeps the color rather than undoing it. The value field is typed, and undoing a failed save would delete what you wrote. The refused color therefore stays on screen and says it was not saved — which is recoverable, because the previous color is one click away in the recents strip.

The field is not marked invalid. aria-invalid means this value is wrong, and a save that failed on the network says nothing about the color you picked.

Popover mode only (popover). The default renders a native <input type="color">, whose value the browser owns — there is no seam there to hook.

Props

Prop Type Default Description
name string|null null Form field name
id string|null auto-generated Element id
value string '#000000' Initial hex color
size string 'md' 'sm', 'md', 'lg'
showValue bool true Show live value next to swatch
disabled bool false Disabled state
popover bool false Use the custom popover picker instead of the native dialog
nativeOnMobile bool false Popover only — open the OS-native color dialog on touch-primary devices (pointer: coarse)
format string 'hex' Popover default format — hex, rgb, hsl, or oklch
withAlpha bool true Show the alpha slider (popover only)
withEyedropper bool true Show the screen eyedropper (where the browser supports it)
withRecents bool true Show the recent-colors history (localStorage)
withClear bool false Popover only — show a clear button that empties the bound value ("no color")
presets array [] Preset swatch colors (popover only)
recentsKey string|null null localStorage key for recents (default wk-color-picker-recents)
scope string|null null Scoped personalization key
optimistic string|null null Livewire method to call once a color is settled, showing it before the server answers. A refusal keeps the color and says it was not saved. Popover mode only. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.

Accessibility

  • Uses native <input type="color"> — full keyboard + AT support
  • <label> wraps the swatch, allowing click-to-open on the visible circle
  • Live value display uses aria-live="polite" for AT announcements on change
  • focus-within ring on the swatch indicates keyboard focus

Keyboard Interaction

Key Action
Tab Move focus to the color picker control
Enter / Space Open the system color dialog
Type a #hexcode value Set the value directly via the text-input fallback

Pitfalls

  • Don't rely on the native color picker for brand-locked palettes. It exposes the entire RGB gamut. For constrained palettes, render <x-wirekit::badge> swatches with wire:click.
  • Don't store the value as rgba(). The component returns #rrggbb (sRGB hex). Store hex; convert to other spaces at render time.

Design Tokens

Token Used for
--font-wk-mono Live hex value font
--text-wk-sm Live hex value font size
--color-wk-text Hex value text
--color-wk-border Swatch border
--color-wk-ring Focus ring
--ring-wk-width Focus ring width
--border-wk-width Swatch border width
--padding-wk-x-sm Hex pill padding
--transition-wk-duration Focus / hover transition

Config Defaults

The defaults live in config/wirekit.php under components.color-picker. Override them globally:

'components' => [
    'color-picker' => ['size' => 'md'],
],

Further Reading

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store