Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

InputsReact

Color Swatch Picker

A choice of preset colors.

Rendered by React 19.3.1

Loading React…
import * as React from 'react'
import { ColorSwatchPicker } from '@hamur/react/color-swatch-picker'

const tokens = [
  { name: 'Danger', value: '#e5484d' },
  { name: 'Warning', value: '#f76b15' },
  { name: 'Caution', value: '#ffc53d' },
  { name: 'Success', value: '#46a758' },
  { name: 'Info', value: '#0090ff' },
  { name: 'Brand', value: '#8e4ec6' },
]

export default function ColorSwatchPickerExample() {
  const [value, setValue] = React.useState<string | null>('#0090ff')
  const token = tokens.find((t) => t.value === value)
  return (
    <div className="field">
      <span id="color-token-label" className="color-swatch-picker-label">
        Accent token
      </span>
      <ColorSwatchPicker.Root
        className="color-swatch-picker"
        aria-labelledby="color-token-label"
        value={value}
        onValueChange={setValue}
      >
        {tokens.map((t) => (
          <ColorSwatchPicker.Item
            key={t.value}
            value={t.value}
            aria-label={t.name}
            className="color-swatch-picker-item"
          />
        ))}
      </ColorSwatchPicker.Root>
      <p className="muted">Selected: {token ? `${token.name} (${token.value})` : 'none'}</p>
    </div>
  )
}
Not measured yetTests passing in React
Not measured yetSize
Not measured yetMemory
Not measured yetSpeed
[N] commits

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<ColorSwatchPicker.Root>
  <ColorSwatchPicker.Item />
</ColorSwatchPicker.Root>

Examples

The demo at the top is the one example this component has so far.

API reference

16 props across 2 parts, in React syntax. className, style, render are listed once, under Shared props.

ItemColorSwatchPicker.Item

A preset color: a radio named by the color's description (or its aria-label), checked when its color equals the value. Renders a <span>.

Item props
PropTypeDefault
valuerequiredThe item's color, any CSS color string.string—
disabledWhether the item cannot be checked.booleanfalse
nativeButtonWhether the component renders a native <button> element when replacing itbooleanfalse
Item data attributes
Data attributeDescription
data-checkedPresent when the item's color is the value.
data-uncheckedPresent when the item's color is not the value.
data-disabledPresent when the item or the swatch picker is disabled.
Item CSS variables
CSS variableDescription
--colorThe item's color.

RootColorSwatchPicker.Root

A radio group of preset colors, checked by parsed equality. Inside ColorPicker.Root it reads and sets the picker's color and renders no hidden input of its own. (and a hidden <input> beside it outside a picker). Renders a <div>.

Root props
PropTypeDefault
valueThe checked color, or null for none.string | null—
defaultValueThe checked color when the component is initially rendered.string | nullnull
onValueChangeEvent handler called when an item is checked, with the item's value.((value: string | null, eventDetails: ColorSwatchPickerRootChangeEventDetails) => void)—
disabledWhether the component should ignore user interaction.booleanfalse
readOnlyWhether the user should be unable to change the color.booleanfalse
requiredWhether the user must choose a color before submitting a form.booleanfalse
loopFocusWhether the arrow keys wrap from the last item to the first and back.booleantrue
labelsThe color name words of the items' names, in English by default.Partial<ColorNameLabels>—
getColorNameNames an item's color in place of the generated name.((color: Color) => string)—
localeThe locale of the transparency percentage in the names.Intl.LocalesArgument—
nameIdentifies the field when a form is submitted.string—
formIdentifies the form that owns the hidden input.string—
inputRefA ref to access the hidden input element.React.Ref<HTMLInputElement>—
Root data attributes
Data attributeDescription
data-disabledPresent when the swatch picker is disabled.
data-readonlyPresent when the swatch picker is read-only.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
TabMoves into the group on the checked item, or the first one when none is checkedRoot
ArrowRightArrowDownMoves to the next item and checks it; from the last it wraps to the firstItem
ArrowLeftArrowUpMoves to the previous item and checks it; in RTL ArrowLeft moves forwardItem
Stop at the first and last item with loopFocus={false}Root
SpaceChecks the focused itemItem
EnterNothing: a swatch is a radio, not a buttonItem

Accessibility

What the parts set
PartSetsSo that
Rootrole="radiogroup", aria-readonly, aria-required, aria-disabled; its name from Field.Label, aria-label or aria-labelledbythe swatches are read as one choice with its name and state
Itemrole="radio", aria-checked, aria-label with the color's name ("vibrant blue", "red, 50% transparent")each swatch says its color, not only shows it
Iteman aria-label of your own wins over the color namea token name ("Info", "Danger") is read where the color stands for one
Itemaria-disabled and tabindex="-1" on a disabled itema disabled swatch stays in the group and the arrows skip it

Focus order

  1. The group is one Tab stop: the checked item, or the first item that is not disabled when nothing is checked. Nothing takes focus on mount, and Tab into the group checks nothing.
  2. The arrows move focus to the item they check.
  3. A read-only group keeps its Tab stop and checks nothing; a disabled one has none.