Forms

Cell Color Picker

A settings-list row that names a colour, shows its value, and opens a picker to change it.

Usage

CellColorPicker is one row of a settings list. The whole row is the button: pressing anywhere on it opens a popover of colour controls. The row itself carries the label, the current value as text, and the swatch it is set to.

It wraps the ColorPicker from @blakeui/react, so the state, the overlay and the keyboard behaviour are React Aria's. What this component adds is the cell-row layout, the controls the popover fills itself with, and the two surface treatments that let a stack of these read as one settings panel.

Anatomy

Import the CellColorPicker component and access all parts using dot notation.

import {CellColorPicker} from "@blakeui/pro-react";

<CellColorPicker>
  <CellColorPicker.Trigger>
    <CellColorPicker.Label />
    <CellColorPicker.ValueDisplay />
    <CellColorPicker.Swatch />
  </CellColorPicker.Trigger>
  <CellColorPicker.Popover />
</CellColorPicker>;

CellColorPicker.Trigger and CellColorPicker.Popover are required; the three parts inside the trigger are each optional, and their order is yours.

An empty CellColorPicker.Popover supplies its own controls — a saturation/brightness area, a hue slider and a hex field — the way Timeline supplies its rail. A settings list is usually several colours long, and hand-composing three controls per row is work the component can do for you. Pass children to replace them entirely.

Controlled

Selected: #E9573F

Pass value with onChange to drive the colour yourself, or defaultValue to set the initial colour and leave the component to manage it. Both accept a CSS colour string or a parsed Color; onChange always hands back a Color.

Disabled

isDisabled on the root disables the row and stops the popover opening. A CellColorPicker.Trigger with its own isDisabled wins over the root's, so one cell in a group can opt out of a disabled set.

Settings Group

Stacked cells are what the component is for. The panel paints nothing behind them and each cell keeps its own stroke, matching how ItemCardGroup groups its cards.

Variants

variant sits on the root and reaches the row through context. Both treatments are bordered-flat — a 1px stroke and no elevation — and only the fill separates them: default sits on --surface, secondary on --surface-secondary.

With Presets

presets on the popover adds a swatch row beneath the controls. It writes to the same state as the area and the slider, so a preset press updates the row immediately and the swatch matching the current colour shows as selected.

CSS Classes

Element Classes

  • .cell-color-picker — The root. display: block and width: 100%, so the cell spans its list rather than hugging its content the way the free .color-picker does.
  • .cell-color-picker__trigger — The visible row, and the <button>. display: flex, justify-content: space-between, gap: calc(var(--spacing) * 3), border: 1px solid var(--border), border-radius: calc(var(--radius) * 2), padding-inline: calc(var(--spacing) * 4), padding-block: calc(var(--spacing) * 2.5), min-height: calc(var(--spacing) * 11) and text-align: start. The 44px floor is the Cell family's row height — a settings row is a touch target rather than a text field — and it is the floor rather than the padding that sets it, so a row whose content grows taller still lays itself out. It sets box-shadow: none: the row is bordered-flat, so the stroke does the separating.
  • .cell-color-picker__label — The leading text. flex: 1 1 auto with min-width: 0 and an ellipsis, which is what pins the value and the swatch to the trailing edge as a pair. font-weight: 500, color: var(--foreground).
  • .cell-color-picker__value-display — The colour as text. color: var(--muted) and font-variant-numeric: tabular-nums, so the row does not twitch as the digits change during a drag.
  • .cell-color-picker__swatch — The colour chip. calc(var(--spacing) * 5) square, border-radius: calc(var(--radius) * 0.75), and box-shadow: inset 0 0 0 1px var(--border) — an inset ring rather than a border, so a pale colour stays visible without losing any of the painted area.
  • .cell-color-picker__popover — The overlay. min-width: calc(var(--spacing) * 62), wide enough for an area and its sliders to share one column. It inherits box-shadow: var(--shadow-overlay) from .color-picker__popover and keeps it: this is a genuinely floating surface.
  • .cell-color-picker__presets — The preset swatch row, present only when presets is passed. border-top: 1px solid var(--separator) with padding-top: calc(var(--spacing) * 3): picking a preset is a different kind of choice from dragging the area, and the rule says so.
  • .cell-color-picker__dialog — The dialog inside the popover. display: flex, flex-direction: column, gap: calc(var(--spacing) * 3). Internal to CellColorPicker.Popover rather than a part you compose.

The free .color-picker, .color-picker__trigger and .color-picker__popover classes are on the same elements. The Pro layer is imported after @blakeui/styles, so where the two set the same property at equal specificity the Pro rule wins on source order.

Motion

The row's own feedback is the free trigger's background-colour transition. It is colour-only, so there is no movement in it to remove and it needs no reduced-motion opt-out.

The popover's entrance and exit belong to @blakeui/styles, not to this component: [data-entering="true"] runs enter at 150ms on --ease-out-quint with a one---spacing directional slide, and [data-exiting="true"] runs exit at 100ms on --ease-out-quad. Its reduced-motion opt-out ships there too, as a motion-reduce: utility that the package's own variant expands to both halves — an explicit [data-reduce-motion="true"] on the popover or any ancestor, and @media (prefers-reduced-motion: reduce) scoped so the two never double-apply. An overlay motion change is a version bump of the free package, never a rule here.

API Reference

CellColorPicker

The root. Wraps the free ColorPicker, which owns the colour state and the overlay trigger, and hands variant and isDisabled to the parts through context. Arbitrary DOM props are not accepted: the free root forwards its rest props to React Aria's state container, which renders none of them.

PropTypeDefaultDescription
childrenReactNodeA CellColorPicker.Trigger and a CellColorPicker.Popover. Required.
valueColor | stringControlled colour. Pair it with onChange.
defaultValueColor | stringInitial colour when uncontrolled.
onChange(color: Color) => voidCalled whenever the colour changes — a drag on the area or a slider, a hex edit, or a preset press.
variant"default" | "secondary""default"Surface treatment of the row. Both are bordered-flat; only the fill differs. Applied to CellColorPicker.Trigger through context.
isDisabledbooleanfalseDisables the cell. A CellColorPicker.Trigger with its own isDisabled wins over this.
classNamestringClass name for the root.

CellColorPicker.Trigger

The visible row, and the only interactive element in the cell. It is the free ColorPicker.Trigger — a React Aria Button under a DialogTrigger — so it carries a button role and aria-expanded natively, and accepts all of that component's props. Its accessible name comes from its own text content.

PropTypeDefaultDescription
childrenReactNodeRow content — a Label, and typically a ValueDisplay and a Swatch. Required.
isDisabledbooleanDisables this row, overriding the root's own value.
classNamestring | (renderProps) => stringClass name for the row. A function receives React Aria's button render props.

CellColorPicker.Label

The leading text. A <span> rather than a <label>: it sits inside the trigger button, so it is already part of that button's accessible name, and label-for-a-button is not a relationship the platform has. Accepts every native <span> attribute.

PropTypeDefaultDescription
childrenReactNodeLabel text. Required.
classNamestringClass name for the label.

CellColorPicker.ValueDisplay

The current colour as text, read live from React Aria's ColorPickerStateContext. It tracks a drag on the area without any local state, and it stays real text rather than decoration: it is the only part of the row that says what the colour is, and it forms part of the trigger's accessible name. Accepts every native <span> attribute except children.

PropTypeDefaultDescription
formatColorFormat"hex"Colour notation to print — "hex", "hexa", "rgb", "rgba", "hsl", "hsla", "hsb" or "hsba".
classNamestringClass name for the value text.

CellColorPicker.Swatch

The colour chip. Wraps the free ColorSwatch and accepts all of its props; it reads the current colour from the context React Aria's ColorPicker publishes, so it needs no color of its own. Decorative by construction — the value display beside it carries the same information as text.

PropTypeDefaultDescription
colorColor | stringOverrides the colour drawn. Left unset — which is the usual case — the swatch follows the picker's state.
classNamestring | (renderProps) => stringClass name for the swatch.

CellColorPicker.Popover

The overlay. Wraps the free ColorPicker.Popover — itself a React Aria Popover — and accepts all of its props. It renders an element with role="dialog" inside, which is what gives the overlay its role and its accessible name. The popover portals to <body>, so it escapes any overflow clipping between it and the row.

PropTypeDefaultDescription
childrenReactNodean area, a hue slider and a hex fieldReplaces the supplied controls. Compose ColorArea, ColorSlider or ColorField from @blakeui/react; each reads and writes the picker's state through React Aria's context.
presetsstring[]Colours offered as a swatch row beneath the controls. Kept alongside children rather than replaced by them.
placementPlacement"bottom end"Preferred side, so the panel lines up under the swatch rather than under the label. React Aria flips it when the chosen side would overflow the viewport, and data-placement reports where it actually landed.
aria-labelstring"Color picker"Accessible name for the popover dialog.
classNamestring | (renderProps) => stringClass name for the popover.

On this page