Skip to content

ColorField

A numeric input component for editing individual color channels, with optional increment/decrement buttons and a color swatch preview.

Preview

Source code
tsx
import "internationalized-color/css";
import { colorSpaces } from "@urcolor/core";
import { ColorField, useColor } from "@urcolor/react";

export default function ColorFieldHSL() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");
  const channels = colorSpaces["hsl"]?.channels ?? [];

  return (
    <div className="flex items-start gap-4">
      <div className="flex flex-1 flex-wrap gap-2">
        {channels.map((ch) => (
          <div key={ch.key} className="flex min-w-[80px] flex-1 flex-col gap-1">
            <label
              htmlFor={`field-${ch.key}`}
              className="text-xs font-semibold text-[var(--vp-c-text-2)]"
            >
              {ch.label}
            </label>
            <ColorField.Root
              value={color}
              onValueChange={setColor}
              colorSpace="hsl"
              channel={ch.key}
              className="
                flex items-center overflow-hidden rounded-md border
                border-[var(--vp-c-divider)] bg-[var(--vp-c-bg)]
              "
            >
              <ColorField.Decrement
                className="
                  flex size-8 shrink-0 cursor-pointer items-center justify-center
                  border-r border-none border-r-[var(--vp-c-divider)] bg-transparent
                  text-lg leading-none text-[var(--vp-c-text-2)] select-none
                  hover:not-disabled:bg-[var(--vp-c-bg-soft)]
                  hover:not-disabled:text-[var(--vp-c-text-1)]
                  disabled:cursor-default disabled:opacity-30
                "
              >
                &minus;
              </ColorField.Decrement>
              <ColorField.Input
                id={`field-${ch.key}`}
                className="
                  w-0 min-w-0 flex-1 border-none bg-transparent px-0.5 py-1
                  text-center font-mono text-[13px] text-[var(--vp-c-text-1)]
                  outline-none
                "
              />
              <ColorField.Increment
                className="
                  flex size-8 shrink-0 cursor-pointer items-center justify-center
                  border-l border-none border-l-[var(--vp-c-divider)] bg-transparent
                  text-lg leading-none text-[var(--vp-c-text-2)] select-none
                  hover:not-disabled:bg-[var(--vp-c-bg-soft)]
                  hover:not-disabled:text-[var(--vp-c-text-1)]
                  disabled:cursor-default disabled:opacity-30
                "
              >
                +
              </ColorField.Increment>
            </ColorField.Root>
          </div>
        ))}
      </div>
    </div>
  );
}

Anatomy

tsx
<ColorField.Root>
  <ColorField.Decrement />
  <ColorField.Input />
  <ColorField.Increment />
</ColorField.Root>

<ColorField.Swatch />

Examples

Hex Input

Source code
tsx
import "internationalized-color/css";
import { ColorField, useColor } from "@urcolor/react";

export default function ColorFieldHex() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  return (
    <div className="flex items-center gap-3">
      <ColorField.Root
        value={color}
        onValueChange={setColor}
        colorSpace="hex"
        channel="hex"
        format="hex"
        className="
          flex h-8 items-center overflow-hidden rounded-md border
          border-[var(--vp-c-divider)] bg-[var(--vp-c-bg)] px-3
        "
      >
        <ColorField.Input
          className="
            min-w-0 flex-1 border-none bg-transparent px-3 py-1.5 font-mono
            text-[13px] text-[var(--vp-c-text-1)] outline-none
          "
        />
      </ColorField.Root>
    </div>
  );
}

HSL Channel Fields

HSL channel inputs with increment/decrement buttons.

Source code
tsx
import "internationalized-color/css";
import { colorSpaces } from "@urcolor/core";
import { ColorField, useColor } from "@urcolor/react";

export default function ColorFieldHSL() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");
  const channels = colorSpaces["hsl"]?.channels ?? [];

  return (
    <div className="flex items-start gap-4">
      <div className="flex flex-1 flex-wrap gap-2">
        {channels.map((ch) => (
          <div key={ch.key} className="flex min-w-[80px] flex-1 flex-col gap-1">
            <label
              htmlFor={`field-${ch.key}`}
              className="text-xs font-semibold text-[var(--vp-c-text-2)]"
            >
              {ch.label}
            </label>
            <ColorField.Root
              value={color}
              onValueChange={setColor}
              colorSpace="hsl"
              channel={ch.key}
              className="
                flex items-center overflow-hidden rounded-md border
                border-[var(--vp-c-divider)] bg-[var(--vp-c-bg)]
              "
            >
              <ColorField.Decrement
                className="
                  flex size-8 shrink-0 cursor-pointer items-center justify-center
                  border-r border-none border-r-[var(--vp-c-divider)] bg-transparent
                  text-lg leading-none text-[var(--vp-c-text-2)] select-none
                  hover:not-disabled:bg-[var(--vp-c-bg-soft)]
                  hover:not-disabled:text-[var(--vp-c-text-1)]
                  disabled:cursor-default disabled:opacity-30
                "
              >
                &minus;
              </ColorField.Decrement>
              <ColorField.Input
                id={`field-${ch.key}`}
                className="
                  w-0 min-w-0 flex-1 border-none bg-transparent px-0.5 py-1
                  text-center font-mono text-[13px] text-[var(--vp-c-text-1)]
                  outline-none
                "
              />
              <ColorField.Increment
                className="
                  flex size-8 shrink-0 cursor-pointer items-center justify-center
                  border-l border-none border-l-[var(--vp-c-divider)] bg-transparent
                  text-lg leading-none text-[var(--vp-c-text-2)] select-none
                  hover:not-disabled:bg-[var(--vp-c-bg-soft)]
                  hover:not-disabled:text-[var(--vp-c-text-1)]
                  disabled:cursor-default disabled:opacity-30
                "
              >
                +
              </ColorField.Increment>
            </ColorField.Root>
          </div>
        ))}
      </div>
    </div>
  );
}

API Reference

ColorField.Root

The root container that manages field state and color channel binding.

PropTypeDefaultDescription
valueColor | string | nullControlled color value.
defaultValueColor | string | nullInitial color when uncontrolled.
colorSpacestring'hsl'Color space mode (e.g. 'hsl', 'oklch', 'hex').
channelstring'h'Channel to control (e.g. 'h', 's', 'l', 'hex').
format'number' | 'degree' | 'percentage' | 'hex'AutoDisplay format. Auto-derived from channel config if omitted.
minnumberAutoMinimum value. Auto-derived from channel config.
maxnumberAutoMaximum value. Auto-derived from channel config.
stepnumberAutoArrow key step increment. Auto-derived from channel config.
disabledbooleanfalseDisables interaction.
readOnlybooleanfalseMakes the field read-only.
onValueChange(color: Color) => voidCalled when color changes.
onValueCommit(color: Color) => voidCalled when interaction ends (blur or Enter).

ColorField.Input

The text input element for entering color channel values.

ColorField.Increment

Button to increment the color field value. Auto-disabled at maximum.

PropTypeDefaultDescription
disabledbooleanfalseDisables the button.

ColorField.Decrement

Button to decrement the color field value. Auto-disabled at minimum.

PropTypeDefaultDescription
disabledbooleanfalseDisables the button.

ColorField.Swatch

Displays a color preview swatch with automatic checkerboard background.

PropTypeDefaultDescription
valueColor | string | nullThe color value to display.
alphabooleanfalseWhen true, reflects the color's alpha channel.
checkerSizenumber16The checkerboard tile size in pixels.

Accessibility

ColorField provides a spinbutton interface for precise numeric color channel editing.

Keyboard Navigation

KeyAction
Arrow UpIncrease by one step
Arrow DownDecrease by one step
Page UpIncrease by 10x step
Page DownDecrease by 10x step
HomeJump to minimum
EndJump to maximum
EnterCommit current value