Skip to content

Diff Bar

Compare values from models, runs or periods, with the better result highlighted.

import { DiffBar } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a diff bar to compare two values of one measure: two models on accuracy, this month's latency against last month's, a candidate against production. Each side grows out from a shared center toward its own end, A to the start and B to the end, in cells you can count, and the better side is lit. Stack them and every row shares one center line, so a table of them reads as a verdict at a glance. For one row's share of a whole use a Share Bar; for a baseline beside the actual, a Hatched Bar.

import { DiffBar, diffWinner } from '@youj/react'

<DiffBar label="Accuracy" a={91.2} b={92.4} format={{ style: 'unit', unit: 'percent' }} />
<DiffBar label="Latency p95" a={1840} b={910} better="lower" format={{ style: 'unit', unit: 'millisecond' }} names={['Large', 'Mini']} />

diffWinner(1840, 910, 'lower') // 'b'

better says which way wins: higher (the default) or lower. A difference under tolerance of the larger value (half a percent by default) is even, and lights neither side. Each side's full length is the larger value, or your max when several bars should share a scale. format and locale set how the values read, segments how many cells each side has (14; 0 draws solid bars), and tone the lit side's color (the accent, a status or a chart color).

layout is diverging (the default, out from a shared center) or stacked (two rows on one scale, the worse side's excess in danger).

diffWinner(a, b, better, tolerance) returns the same verdict ('a', 'b' or 'even'), for a chip or a count of wins beside the bars.

Reference

Propsa, b, max… (14)

Props

DiffBar props
PropType
aRequirednumber

The start side's value.

–
bRequirednumber

The end side's value.

–
maxnumber

Each side's full length. Default: the larger of the two.

–
better'higher' | 'lower'

Which way is better.

default 'higher'

'higher'
labelReactNode

The measure, shown above the bar: "Accuracy".

–
names[string, string]

The sides' names for the verdict.

default ['A', 'B']

['A', 'B']
formatIntl.NumberFormatOptions

Intl.NumberFormat options for the values.

–
localestring

Default: the YoujProvider locale.

–
segmentsnumber

Cells per side.

default 14

14
toneDiffBarTone
'accent''success''chart-1''chart-2''chart-3''chart-4''chart-5'

The better side's tone.

default 'accent'

'accent'
tolerancenumber

A difference under this share of the larger value is even.

default 0.005

0.005
bystring

The margin as spoken in the verdict. Default: the relative difference, as a percentage.

–
showValuesboolean

Show the values at the ends, each in a column --diff-bar-value wide (4.5rem), so stacked bars share one center.

default true

true
layout'diverging' | 'stacked'

diverging (default): the sides grow out from a shared center. stacked: two rows on one scale, named by names, the worse side's excess in danger.

default 'diverging'

'diverging'
Other props pass through: extends ComponentProps<'div'>.
StylingParts and data attributes

Styling

Parts: diff-bar (with data-winner, and data-variant: diverging or stacked), diff-bar-name (stacked), diff-bar-label, diff-bar-row, diff-bar-value and diff-bar-track (each with data-side), diff-bar-cell (with data-state), diff-bar-bar, diff-bar-fill (stacked: with data-part, shared or excess), diff-bar-center and diff-bar-verdict. The value columns are --diff-bar-value wide (4.5rem), so stacked bars share one center; widen it for longer values.

MotionFirst view, new value · reduced motion

Motion

Motion tokens
MomentTokens
First viewboth sides light outward from the center at one constant rate, so the longer side takes longer, once half the bar is in view
duration.deliberate

560ms

560ms
New valuecells light or darken in place
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Drawn at once.

Reduced motion

The cells are lit at once, and a new value lands in place.

AccessibilityARIA

Accessibility

  • The values are text at the bar's ends, read in order, and a visually hidden verdict follows them: "Mini is better by 51%", or "Even". The margin is relative to the worse side unless you pass by ("1.2 points").
  • names name the sides in the verdict (['A', 'B'] by default).
  • The cells are decorative (aria-hidden).
  • The start side grows toward the inline start, so the bar mirrors in RTL.
  • Its words come from the labels (diffBetter, diffEven), translated by the language packs.