KQ-1676 · MIXED/VALUES

Mixed Values

A bulk edit form over a selection of records: a field whose values differ shows them as a chip of variants with counts — a deck that fans out on springs.snap when you reach the field, a share bar, or a row — and typing or choosing a new value collapses the chips into the first on springs.glide while the new-value chip pops in on snap. Committing the value sends a ripple down the records list beside the form, each record the change touches lighting in a cascade with its diff, while records that already hold the value stay still and fields left alone keep every original. A preview names how many records each change touches, Apply reports only the changed fields and turns into a bar that glides with each record's result while rows tick or cross, and failures are listed with a Retry failed; Tab and Enter work throughout and Escape takes a field back.

KQ-1676 · MIXED/VALUES

Edit products

14 products · no changes yet

Mixed: Active on 9, Draft on 4, Paused on 1.
Mixed: Medium on 7, Dark on 4, Light on 3.
Mixed: $12.50 on 4, $14.00 on 4, $16.00 on 2, $38.00 on 2, $42.00 on 2.
Mixed: A2 on 7, B1 on 5, Back on 2.
Same on all 14

Preview

Nothing changes yet. Fields left alone keep every original value.

no changes · 14 of 14 products selected

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/mixed-values

Props

records
MixedValuesRecord[]
default defaultMixedValuesRecords
The records being edited: { id, name, values } with values as text (money as a decimal).
fields
MixedValuesField[]
default defaultMixedValuesFields
The form: { id, label, kind?, options?, suffix?, validate? }.
selected / defaultSelected
string[]
default — / every record
The selection, controlled or initial.
onSelectedChange
(ids) => void
default —
Fires from the checkbox that changed the selection.
onChange
(changes) => void
default —
Fires from the keystroke or choice that changed the pending edit, with only the changed fields.
onApply
(changes, ids, report) => void | Promise
default —
Apply was pressed with the changed fields and the records they touch; call report(id, ok, reason?) per record, return a promise to show progress until it settles.
onCancel
(changes) => void
default —
Cancel was pressed, with the changes being dropped.
chipStyle
"stack" | "bar" | "inline"
default "stack"
How differing values show: a deck that fans on focus, a share bar, or every value in a row.
density
"compact" | "regular" | "roomy"
default "regular"
Field and row heights.
accent
string
default "var(--accent-bright)"
The new-value chip, the ripple, Apply and progress — any CSS colour; buttons take its hue at a fixed lightness so white text reads in both themes.
itemLabel
{ one: string; other: string }
default { one: "product", other: "products" }
What one record is called in counts and announcements.
heading
string
default "Edit products"
The panel's heading and accessible name.
currency
string
default "USD"
The currency for money fields.
locale
string
default "en-US"
The locale for money fields.
status
"ready" | "loading" | "error"
default "ready"
Loading shows skeleton fields; error shows Try again.
onRetry
() => void
default —
Try again was pressed after the records failed to load.
height
number
default 540
The panel's height in px; the form and the list scroll inside it.
disabled
boolean
default false
Nothing can be edited or applied.
className
string
default —
Classes for the panel.