Histogram Range
Choose a range while seeing the distribution of values it filters.
import { HistogramRange } from '@youj/react'
Installation & usage
npm i @youj/reactFor 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 histogram range when the shape of the data should guide the cut: file sizes, prices, response times, scores. People see where the values bunch up before they choose, and the bars inside the range show what they're keeping. For a range on its own, a Slider with two values is enough.
import { useState } from 'react'
import { HistogramRange } from '@youj/react'
export function SizeFilter({ bins }: { bins: number[] }) {
const [range, setRange] = useState<[number, number]>([4.8, 38])
return (
<HistogramRange
label="File size"
bins={bins}
min={0}
max={64}
step={0.1}
value={range}
onValueChange={setRange}
format={(v) => `${v.toFixed(1)} MB`}
/>
)
}Pass counts you've already binned as bins, evenly spaced from min to max, or raw values as data and it counts them into binCount bins (40 by default); values outside min and max are left out. It's controlled with value and onValueChange, or uncontrolled with defaultValue; onValueCommitted fires once a drag or key press ends.
format turns a value into text: the readout above the plot, what a screen reader says for each thumb, and the axis. formatTick formats the axis on its own (say "0" rather than "0.0 MB"), and ticks picks the values it marks.
The accent stays on the track between the thumbs and on the readout, and the bars in range are a stronger neutral. tone="accent" puts the accent on those bars too. Each thumb is drawn as a caliper's jaw: an upright tab on the track with a hairline guide that rises through the plot, so you read each end of the cut against the bars it falls in.
Description
A file-size filter: a range from 0 to 64 MB drawn over a histogram of uploads.
The bars gliding to new heights as the data changes.
When the data changes, the bars glide to their new heights; the range stays where it was.
Code
histogram-range-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsdata, bins, binCount… (18)
Props
| Prop | Type | |
|---|---|---|
data | readonly number[]Raw values, counted into | – |
bins | readonly number[]Counts per bin, evenly spaced from | – |
binCount | numberHow many bins default 40 | 40 |
min | numberdefault 0 | 0 |
max | numberdefault 100 | 100 |
step | numberdefault 1 | 1 |
value | Range | – |
defaultValue | Rangedefault [min, max] | [min, max] |
onValueChange | (value: Range) => void | – |
onValueCommitted | (value: Range) => voidCalled once a drag or key press ends, with the final range. | – |
label | stringThe visible name over the plot ("Duration"). It names the group, and the thumbs as its minimum and maximum. | – |
format | (value: number) => stringFormats a value for the readout, the thumbs' spoken values and, unless | – |
formatTick | (value: number) => stringFormats an axis tick. default `format` | `format` |
ticks | readonly number[]The values the axis marks. Default: four, evenly spaced from | – |
tone | 'neutral' | 'accent'The bars inside the range: a stronger neutral, or the accent. default 'neutral' | 'neutral' |
thumbLabels | [string, string]Names for the two thumbs. Default: the label's minimum and maximum. | – |
name | stringAlso submits both ends with a native form, under this name. | – |
disabled | boolean | – |
Other props pass through: extends ComponentProps<'div'>. | ||
MotionThumbs and range, data changes, first paint… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Thumbs and rangetrack the pointer 1:1; the bars in range are a copy clipped to it, so a bar under a thumb is cut at its center | no token no easing | no easing | |
| Data changeseach bar’s height glides (a clip-path, so corners stay crisp) in a quick wave from the first bin | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| First paintthe bars rise from the baseline the same way | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| Thumb halothe Slider’s halo on hover, focus and drag | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) |
Reduced motion
The bars take their heights in one frame, on first paint and on every change. The thumbs and the range were direct already.
Reduced motion
Only the bars move, so only they change: they take their new heights at once. Dragging was never animated.
AccessibilityKeyboard, announcements
Accessibility
- The component is a
groupnamed bylabel. Its thumbs are the Slider's: each is aslidernamed as the label's minimum and maximum ("Minimum file size"), or bythumbLabels, and speaks its value throughformat. - The arrow keys step, Page Up, Page Down and Shift with an arrow take large steps, and Home and End jump to the ends. The whole plot is the hit area: a press anywhere moves the nearest thumb.
- The bars and the axis are hidden from assistive tech. The readout is spoken through a polite live region half a second after the range stops changing, not on every step.
- Under right-to-left, the minimum sits on the right, and the bars, the accent and the axis mirror with the thumbs.