Skip to content
omaris

Inputs

Color Picker

A colour picker that thinks in OKLCH, because this library does.

import { ColorPicker } from 'omaris'
Learn

Examples

Basic

The plane is lightness up and chroma across, each row scaled to the most colour that lightness can hold — so it is full of colour and everything in it is a colour sRGB can show. plane="absolute" scales every row alike instead, which draws the gamut's real silhouette against transparency. value is plain CSS text in format.

oklch(0.6 0.18 260)
<script lang="ts">	import { ColorPicker, Text } from 'omaris';​	let value = $state('oklch(0.6 0.18 260)');</script>​<div class="flex w-full flex-col items-center gap-3">	<ColorPicker bind:value />	<Text variant="label-md" tone="muted" font="mono">{value}</Text></div>

Format and swatches

format decides how value is written — parsing always accepts hex, rgb(), oklch() and oklab() whatever it says. alpha carries opacity in the value, and swatches puts presets under the picker.

#1c6fd6
<script lang="ts">	import { ColorPicker, SegmentedButton, Text } from 'omaris';​	const BRAND = ['#1c6fd6', '#0f9d58', '#e0a800', '#c0392b', '#6c3fd1', '#111827'];​	let format = $state<'hex' | 'rgb' | 'oklch'>('hex');	let value = $state('#1c6fd6');</script>​<div class="flex w-full flex-col items-center gap-3">	<SegmentedButton		label="Format"		size="sm"		value={format}		onchange={(next) => (format = next as typeof format)}		items={[			{ value: 'hex', label: 'hex' },			{ value: 'rgb', label: 'rgb' },			{ value: 'oklch', label: 'oklch' }		]}	/>​	<ColorPicker bind:value {format} alpha swatches={BRAND} />​	<Text variant="label-md" tone="muted" font="mono">{value}</Text></div>

Compact and sizes

compact trims the picker to the plane, the sliders and the swatches — no text field, no number boxes — for one that has to sit in a settings panel. channels={false} hides the L / C / H boxes on its own, and classes reaches every part — here the root and the swatches.

compact
size="sm"
<script lang="ts">	import { ColorPicker, Text } from 'omaris';​	let accent = $state('oklch(0.7 0.16 150)');	let small = $state('oklch(0.55 0.22 25)');</script>​<div class="flex w-full flex-wrap items-start justify-center gap-8">	<div class="flex flex-col gap-2">		<Text variant="label-sm" tone="muted">compact</Text>		<ColorPicker			bind:value={accent}			compact			channels={false}			eyedropper={false}			swatches={['oklch(0.7 0.16 150)', 'oklch(0.6 0.18 260)', 'oklch(0.75 0.16 70)']}			classes={{				root: 'rounded-shape-lg bg-surface-container-low p-3',				swatch: 'size-7 rounded-shape-sm'			}}			class="w-64 max-w-full"		/>	</div>​	<div class="flex flex-col gap-2">		<Text variant="label-sm" tone="muted">size="sm"</Text>		<ColorPicker bind:value={small} size="sm" label="Accent colour" />	</div></div>

Plane scale

plane scales the x-axis. relative (the default) gives each row the most chroma that lightness can hold, so the box is full of colour and every point in it is pickable. absolute gives every row the same chroma and leaves the rest transparent — the gamut's true silhouette, and mostly checkerboard you cannot drag to.

plane="relative"
plane="absolute"
<script lang="ts">	import { ColorPicker, Text } from 'omaris';​	let value = $state('oklch(0.6 0.18 260)');</script>​<div class="flex w-full flex-wrap items-start justify-center gap-8">	<div class="flex flex-col gap-2">		<Text variant="label-sm" tone="muted">plane="relative"</Text>		<ColorPicker bind:value plane="relative" compact size="sm" />	</div>	<div class="flex flex-col gap-2">		<Text variant="label-sm" tone="muted">plane="absolute"</Text>		<ColorPicker bind:value plane="absolute" compact size="sm" />	</div></div>

Eyedropper

The eyedropper samples a pixel from anywhere on the screen — another tab, another window, the desktop — not just from this page. It is the same capability the picker's own button uses, exported as sampleScreenColor() so it can sit beside a plain field instead. canSampleScreen() says whether to draw the control at all; Firefox and Safari have no sampler.

Outside sRGB — shown at chroma 0.185.

no screen sampler in this browser
<script lang="ts">	import { onMount } from 'svelte';	import { Button, ColorPicker, Text, canSampleScreen, sampleScreenColor } from 'omaris';​	let value = $state('oklch(0.72 0.19 45)');	// Read once the page is mounted: the sampler is a browser capability, so	// on the server the answer is always no and would never be revisited.	let supported = $state(false);	onMount(() => {		supported = canSampleScreen();	});​	async function sample() {		const hex = await sampleScreenColor();		if (hex) value = hex;	}</script>​<div class="flex w-full flex-col items-center gap-4">	<ColorPicker bind:value compact />​	<div class="flex items-center gap-2">		<span class="size-8 shrink-0 rounded-full border border-border" style:background-color={value}		></span>		<Button variant="tonal" size="sm" disabled={!supported} onclick={sample}>			Pick from screen		</Button>	</div>​	<Text variant="label-md" tone="muted" font="mono">		{supported ? value : 'no screen sampler in this browser'}	</Text></div>

When to use it

Use it for

  • A colour that becomes a token: a brand accent, a project colour, a chart series. format="oklch" writes what app.css reads.
  • A tint with opacity, like an overlay or a highlight. Pass alpha; the colour is drawn over a checkerboard.
  • A picker inside a settings panel. compact keeps the plane, the sliders and the swatches and drops the text and number fields.
  • A legacy field that needs #rrggbb. Use format="hex". Parsing accepts hex, rgb(), oklch() and oklab() whatever the format.
  • Matching a colour on screen but outside the app, like a logo or a screenshot. The eyedropper samples any pixel the browser can see; sampleScreenColor() puts that on a button of your own.

Not for

  • A choice between a few named colours, like six tag colours → a row of Chips or a Radio group drawn as swatches. swatches here still opens the whole plane.
  • Switching the app's palette, contrast and radius → Theme Settings.
  • An emoji or icon for a channel → Emoji Picker.
  • A gradient or a palette of several stops. It picks one colour.

Do

  • Leave plane="relative". absolute draws the gamut's true shape, most of which cannot be picked. It is for teaching, not choosing.
  • Pass swatches with the brand's colours, so the common pick is one tap.
  • Let an out-of-gamut value stay as it is. A saved oklch() past sRGB shows a hint and pins the puck to the edge instead of clipping silently.
  • Set channels={false} where the text field is enough. The L / C / H boxes are for someone typing a colour.
  • Use size="md" or larger where a finger does the picking. Everything grows on a coarse pointer; size="sm" is a mouse size.

Don't

  • Store hex when the source of truth is an oklch token. The round trip to sRGB lowers chroma.
  • Put it in a popover with no width. The plane takes the container's width.
  • Hide eyedropper because some browsers lack it. It hides itself where there is nothing to sample with.
  • Call sampleScreenColor() from anything but a click. Browsers only open the sampler inside a user gesture; a call from an effect resolves null.

Quick reference

size
  • sm
  • md (default)
  • lg

API

ColorPicker

A colour picker that thinks in OKLCH, because this library does.

Lightness runs up the y-axis and chroma across the x-axis. How far across is plane:

- relative (the default) scales the row to the most colour that lightness can hold, so the rectangle is full of colour, the right edge is the most saturated this hue gets here, and every point you can drag to is a colour sRGB can show. Moving lightness or hue takes the chroma with it, which is why the puck never falls out of the gamut on its own. - absolute scales every row to the same chroma and leaves the rest transparent, drawing the gamut's real silhouette — the lopsided leaf that grows and shrinks as you turn the hue. True to the geometry, and mostly empty: at any one lightness sRGB holds a fraction of the widest chroma it holds anywhere, so most of the box is checkerboard you cannot pick from.

Either way nothing is clipped behind your back: a colour outside sRGB — typed in, or bound from elsewhere — says so, and asking for hex maps it back by lowering chroma only, so your hue and lightness survive.

value is a plain CSS string in whatever format you asked for, so it drops straight into a token, a form field or a style attribute.

import { ColorPicker } from 'omaris'
<ColorPicker bind:value format="oklch" swatches={brand} />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
area
The lightness × chroma plane.
canvas
No description in the source yet.
areaThumb
The puck on the plane.
controls
Preview + sliders.
preview
The current colour, over a checkerboard.
previewFill
No description in the source yet.
sliders
No description in the source yet.
slider
One slider row.
alphaTrack
The alpha track's checkerboard sits under its gradient.
track
No description in the source yet.
thumb
No description in the source yet.
fields
Numeric and text inputs.
field
No description in the source yet.
label
No description in the source yet.
input
No description in the source yet.
eyedropper
The "sample a pixel" button, where the browser has one.
swatches
No description in the source yet.
swatch
No description in the source yet.
hint
Says when the colour has been mapped back into sRGB.

Props

value bindable

Defaults to 'oklch(0.6 0.18 260)'

string

The colour, as CSS text in format. Bindable.

format

Defaults to 'oklch'

ColorFormat

How value is written. Parsing accepts hex, rgb(), oklch() and oklab() whatever this says.

size

Defaults to 'md'

ColorPickerSize
sm
md
lg
alpha

Defaults to false

boolean

Show the alpha slider and carry alpha in value.

swatches
string[]

Preset colours under the picker. Any CSS colour string.

plane

Defaults to 'relative'

PlaneScale

How the plane's x-axis is scaled — relative fills it with the colour this lightness can hold, absolute draws the gamut's own silhouette against transparency.

channels

Defaults to true

boolean

Show the L / C / H number boxes beside the text field.

eyedropper

Defaults to true

boolean

Offer the browser's screen sampler, where there is one.

compact

Defaults to false

boolean
disabled

Defaults to false

boolean
label

Defaults to 'Colour'

string

Accessible name for the plane.

onchange
(value: string, color: Oklch) => void
class
string
classes
ColorPickerClasses