ColorField
A numeric input component for editing individual color channels, with optional increment/decrement buttons and a color swatch preview.
Preview
Source code
<script setup lang="ts">
import { computed } from "vue";
import "internationalized-color/css";
import { colorSpaces } from "@urcolor/core";
import { Label } from "reka-ui";
import {
ColorFieldRoot,
ColorFieldInput,
ColorFieldIncrement,
ColorFieldDecrement,
useColor,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
const channels = computed(() => colorSpaces["hsl"]?.channels ?? []);
</script>
<template>
<div class="flex items-start gap-4">
<div class="flex flex-1 flex-wrap gap-2">
<div
v-for="ch in channels"
:key="ch.key"
class="flex min-w-[80px] flex-1 flex-col gap-1"
>
<Label
:for="`field-${ch.key}`"
class="text-xs font-semibold text-(--vp-c-text-2)"
>{{ ch.label }}</Label>
<ColorFieldRoot
v-model="color"
color-space="hsl"
:channel="ch.key"
class="
flex items-center overflow-hidden rounded-md border
border-(--vp-c-divider) bg-(--vp-c-bg)
"
>
<ColorFieldDecrement
class="
flex size-8 shrink-0 cursor-pointer items-center justify-center
border-r border-none border-r-(--vp-c-divider) bg-transparent
text-lg leading-none text-(--vp-c-text-2) select-none
hover:not-disabled:bg-(--vp-c-bg-soft)
hover:not-disabled:text-(--vp-c-text-1)
disabled:cursor-default disabled:opacity-30
"
>
−
</ColorFieldDecrement>
<ColorFieldInput
:id="`field-${ch.key}`"
class="
w-0 min-w-0 flex-1 border-none bg-transparent px-0.5 py-1
text-center font-mono text-[13px] text-(--vp-c-text-1)
outline-none
"
/>
<ColorFieldIncrement
class="
flex size-8 shrink-0 cursor-pointer items-center justify-center
border-l border-none border-l-(--vp-c-divider) bg-transparent
text-lg leading-none text-(--vp-c-text-2) select-none
hover:not-disabled:bg-(--vp-c-bg-soft)
hover:not-disabled:text-(--vp-c-text-1)
disabled:cursor-default disabled:opacity-30
"
>
+
</ColorFieldIncrement>
</ColorFieldRoot>
</div>
</div>
</div>
</template>Anatomy
<template>
<ColorFieldRoot>
<ColorFieldDecrement />
<ColorFieldInput />
<ColorFieldIncrement />
</ColorFieldRoot>
<ColorFieldSwatch />
</template>Examples
Hex Input
Source code
<script setup lang="ts">
import "internationalized-color/css";
import {
ColorFieldRoot,
ColorFieldInput,
useColor,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
</script>
<template>
<div class="flex items-center gap-3">
<ColorFieldRoot
v-model="color"
color-space="hex"
channel="hex"
format="hex"
class="
flex h-8 items-center overflow-hidden rounded-md border
border-(--vp-c-divider) bg-(--vp-c-bg) px-3
"
>
<ColorFieldInput
class="
min-w-0 flex-1 border-none bg-transparent px-3 py-1.5 font-mono
text-[13px] text-(--vp-c-text-1) outline-none
"
/>
</ColorFieldRoot>
</div>
</template>HSL Channel Fields
HSL channel inputs with increment/decrement buttons.
Source code
<script setup lang="ts">
import { computed } from "vue";
import "internationalized-color/css";
import { colorSpaces } from "@urcolor/core";
import { Label } from "reka-ui";
import {
ColorFieldRoot,
ColorFieldInput,
ColorFieldIncrement,
ColorFieldDecrement,
useColor,
} from "@urcolor/vue";
const { color } = useColor("hsl(210, 80%, 50%)");
const channels = computed(() => colorSpaces["hsl"]?.channels ?? []);
</script>
<template>
<div class="flex items-start gap-4">
<div class="flex flex-1 flex-wrap gap-2">
<div
v-for="ch in channels"
:key="ch.key"
class="flex min-w-[80px] flex-1 flex-col gap-1"
>
<Label
:for="`field-${ch.key}`"
class="text-xs font-semibold text-(--vp-c-text-2)"
>{{ ch.label }}</Label>
<ColorFieldRoot
v-model="color"
color-space="hsl"
:channel="ch.key"
class="
flex items-center overflow-hidden rounded-md border
border-(--vp-c-divider) bg-(--vp-c-bg)
"
>
<ColorFieldDecrement
class="
flex size-8 shrink-0 cursor-pointer items-center justify-center
border-r border-none border-r-(--vp-c-divider) bg-transparent
text-lg leading-none text-(--vp-c-text-2) select-none
hover:not-disabled:bg-(--vp-c-bg-soft)
hover:not-disabled:text-(--vp-c-text-1)
disabled:cursor-default disabled:opacity-30
"
>
−
</ColorFieldDecrement>
<ColorFieldInput
:id="`field-${ch.key}`"
class="
w-0 min-w-0 flex-1 border-none bg-transparent px-0.5 py-1
text-center font-mono text-[13px] text-(--vp-c-text-1)
outline-none
"
/>
<ColorFieldIncrement
class="
flex size-8 shrink-0 cursor-pointer items-center justify-center
border-l border-none border-l-(--vp-c-divider) bg-transparent
text-lg leading-none text-(--vp-c-text-2) select-none
hover:not-disabled:bg-(--vp-c-bg-soft)
hover:not-disabled:text-(--vp-c-text-1)
disabled:cursor-default disabled:opacity-30
"
>
+
</ColorFieldIncrement>
</ColorFieldRoot>
</div>
</div>
</div>
</template>Alpha Channel
Set channel="alpha" to create an opacity input (0–100%).
<template>
<ColorFieldRoot
:model-value="color"
color-space="hsl"
channel="alpha"
@update:model-value="onColorUpdate"
>
<ColorFieldDecrement>−</ColorFieldDecrement>
<ColorFieldInput />
<ColorFieldIncrement>+</ColorFieldIncrement>
</ColorFieldRoot>
</template>API Reference
ColorFieldRoot
The root container that manages field state and color channel binding.
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | Color | string | null | — | Controlled color value (v-model). |
colorSpace | string | 'hsl' | Color space mode (e.g. 'hsl', 'oklch', 'hex'). |
channel | string | 'h' | Channel to control (e.g. 'h', 's', 'l', 'hex'). |
format | 'number' | 'degree' | 'percentage' | 'hex' | Auto | Display format. Auto-derived from channel config if omitted. |
min | number | Auto | Minimum value. Auto-derived from channel config. |
max | number | Auto | Maximum value. Auto-derived from channel config. |
step | number | Auto | Arrow key step increment. Auto-derived from channel config. |
disabled | boolean | false | Disables interaction. |
readOnly | boolean | false | Makes the field read-only. |
name | string | — | Hidden input name for form submission. |
required | boolean | false | Marks as required for form submission. |
| Event | Payload | Description |
|---|---|---|
update:modelValue | Color | undefined | Emitted when color changes. |
valueCommit | Color | Emitted when interaction ends (blur or Enter). |
ColorFieldInput
The text input element for entering color channel values.
ColorFieldIncrement
Button to increment the color field value. Auto-disabled at maximum. Supports press-and-hold for continuous increment.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the button. |
ColorFieldDecrement
Button to decrement the color field value. Auto-disabled at minimum. Supports press-and-hold for continuous decrement.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the button. |
ColorFieldSwatch
Displays a color preview swatch. Wraps ColorSwatchRoot with automatic checkerboard background for alpha visualization.
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | Color | string | null | — | The color value to display. |
alpha | boolean | false | When true, reflects the color's alpha channel. |
checkerSize | number | 16 | The checkerboard tile size in pixels. |
Accessibility
ColorField provides a spinbutton interface for precise numeric color channel editing with keyboard-driven increment/decrement controls.
ARIA Labels
| Attribute | Description |
|---|---|
role="spinbutton" | Applied to the input element for screen reader recognition. |
aria-label | Labels the input with the channel name. |
aria-valuemin / aria-valuemax | Defines the channel's value range. |
aria-valuenow | Current numeric value of the channel. |
Keyboard Navigation
| Key | Action |
|---|---|
| Arrow Up | Increase by one step |
| Arrow Down | Decrease by one step |
| Page Up | Increase by 10x step |
| Page Down | Decrease by 10x step |
| Home | Jump to minimum |
| End | Jump to maximum |
| Enter | Commit current value |