Value diff
A field's value before and after a change: the old value struck through, an arrow, the new one. Values draw as the table draws them, so a stage stays a pill and money stays money.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
import { ValueDiff } from '@/ui/value-diff';<ValueDiff label="Notice period" after="1 month" before="3 months" />Inline
One field in a sentence or a row: the label in subtle text, the old value struck through in subtle, an arrow, then the new value in medium weight. It takes the type size of the text around it.
import { ValueDiff } from '@/ui/value-diff';
export default function ValueDiffInline() {
return <ValueDiff label="Notice period" before="3 months" after="1 month" />;
}A stage
The new value draws as the table draws it. A stage is a lozenge in its phase hue; the old stage is plain struck text.
import { Lozenge } from '@/ui/lozenge';
import { ValueDiff } from '@/ui/value-diff';
export default function ValueDiffStage() {
return <ValueDiff before="Long list" after={<Lozenge phase="client">CV sent</Lozenge>} />;
}Change list
Several fields: labels in a column of subtle text, each diff beside its label. Used on receipts, approval cards and Activity.
- Stage
- from
Long listtoCV sent - Salary
- from
£95,000 a yearto£110,000 a year - Notice period
- 1 month
- Owner
- from
Priya ShahtoEmpty
import { Lozenge } from '@/ui/lozenge';
import { RecordChip } from '@/ui/record-chip';
import { ValueDiffList } from '@/ui/value-diff';
export default function ValueDiffChangeList() {
return (
<div className="max-w-120">
<ValueDiffList
items={[
{ label: 'Stage', before: 'Long list', after: <Lozenge phase="client">CV sent</Lozenge> },
{ label: 'Salary', before: '£95,000 a year', after: '£110,000 a year' },
{ label: 'Notice period', after: '1 month' },
{ label: 'Owner', before: <RecordChip type="member" name="Priya Shah" />, after: null },
]}
/>
</div>
);
}Empty before or after
A field that was empty shows only the new value. A field that was cleared reads "Empty", never a dash.
import { ValueDiff } from '@/ui/value-diff';
export default function ValueDiffEmpty() {
return (
<div className="flex flex-col items-start gap-100">
<ValueDiff label="Notice period" after="1 month" />
<ValueDiff label="Owner" before="Priya Shah" after={null} />
</div>
);
}Long value
A text value over 160 characters shows two lines and Show all, which opens it in place.
- Summary
- Leads the data platform at Halcyon Bank: a 40 TB lakehouse on Spark and Delta, a team of six, and the move off the old warehouse in 2025. Before that, four years at Northwind Labs building their streaming pipeline.
import { ValueDiffList } from '@/ui/value-diff';
const summary =
'Leads the data platform at Halcyon Bank: a 40 TB lakehouse on Spark and Delta, a team of six, and the move off the old warehouse in 2025. Before that, four years at Northwind Labs building their streaming pipeline.';
export default function ValueDiffLong() {
return (
<div className="max-w-120">
<ValueDiffList items={[{ label: 'Summary', after: summary }]} />
</div>
);
}Big numbers, other scripts, long links
Values show as stored, with their own separators and characters. A long value with no spaces wraps instead of overflowing.
- Salary
- from
£95,000to£1,234,567 - Preferred name
- from
MinhtoNguyễn Thị Minh Khai - Website
- https://careers.halcyonbank.com/engineering/data-platform/senior-data-engineer-london
import { ValueDiffList } from '@/ui/value-diff';
export default function ValueDiffExtremes() {
return (
<div className="max-w-120">
<ValueDiffList
items={[
{ label: 'Salary', before: '£95,000', after: '£1,234,567' },
{ label: 'Preferred name', before: 'Minh', after: 'Nguyễn Thị Minh Khai' },
{ label: 'Website', after: 'https://careers.halcyonbank.com/engineering/data-platform/senior-data-engineer-london' },
]}
/>
</div>
);
}