Mixed Value Field

In a bulk edit, shows "Mixed (3 values)" with the spread instead of a blank, so saving doesn't silently overwrite every record.

StopsBulk edit overwrites every record

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/mixed-value-field.json

The files land in your project. They're yours to edit.

Usage

mixed-value-field-usage.tsx
import { countChanged, MixedValueField, type BulkChange } from "@/components/ui/mixed-value-field"

<MixedValueField
  label="Status"
  values={status}
  options={statusOptions}
  value={statusChange}
  onValueChange={setStatusChange}
  formatItems={tasks}
/>

What it catches

  • Mixed values hidden behind a blank or the first record's value
  • Saving overwrites records nobody meant to touch: nothing changes unless a value is picked
  • Count of records that would really change, skipping those that already match
  • Clearing a field on every record: explicit Clear button

Props

MixedValueField props
PropTypeDefault
label* (required)

Field name, e.g. "Status".

string—
values* (required)

The field's current value on each selected record. Empty strings count as empty.

(string | null | undefined)[]—
options

Choices for a select. Leave out for a free-text field.

{ value: string; label?: string }[]—
value

Controlled change.

BulkChange—
defaultValue

Initial change when uncontrolled.

BulkChangenull
onValueChange

Called with the planned change: null keeps values, { set } writes one value.

(change: BulkChange) => void—
allowClear

Offer "Clear on all" so emptying the field is an explicit choice, never an accident.

booleanfalse
formatItems

"40 tasks". Override to name your records or translate.

(count: number) => stringdefaultItems
mixedText

Templates: `{count}`, `{field}`, `{value}`, `{changed}`, `{total}`, `{already}`, `{items}` are filled in.

string"Mixed ({count} values)"
uniformText

Shown when every record has the same value.

string"{value} on all"
emptyText

Label for empty values.

string"Empty"
keepText

Readback when nothing will change.

string"{field} stays as it is on each of the {items}."
setText

Readback template for setting a value.

string"Set {field} to {value} on {changed} of {items}."
alreadyText

Readback for records that already hold the value.

string"{already} already {value}."
clearText

Readback template for clearing the field.

string"Clear {field} on {changed} of {items}."
noopText

Shown when the change alters no records.

string"All {items} are already {value}. Nothing changes."
clearLabel

Clear button text.

string"Clear on all"
revertLabel

Revert button text.

string"Keep as is"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible label; the control is described by the readback
  • Clear uses aria-pressed; Revert is a button
  • The distribution bar is aria-hidden and summarised in text

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Works on one field at a time
  • The save itself is yours; it only reports the planned change

What it stops

Bulk edit overwrites every record

Also known as

bulk edit · batch edit · mixed values · multi select · table actions · admin · multiple records · mass update

Field type

Dependencies

Pairs well with