Skip to content

Histogram Range

Choose a range while seeing the distribution of values it filters.

import { HistogramRange } 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 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.

Reference

Propsdata, bins, binCount… (18)

Props

HistogramRange props
PropType
datareadonly number[]

Raw values, counted into binCount even bins from min to max. Values outside are left out.

–
binsreadonly number[]

Counts per bin, evenly spaced from min to max. Used instead of data.

–
binCountnumber

How many bins data is counted into.

default 40

40
minnumber

default 0

0
maxnumber

default 100

100
stepnumber

default 1

1
valueRange–
defaultValueRange

default [min, max]

[min, max]
onValueChange(value: Range) => void–
onValueCommitted(value: Range) => void

Called once a drag or key press ends, with the final range.

–
labelstring

The visible name over the plot ("Duration"). It names the group, and the thumbs as its minimum and maximum.

–
format(value: number) => string

Formats a value for the readout, the thumbs' spoken values and, unless formatTick is set, the axis. Default: the locale's number format.

–
formatTick(value: number) => string

Formats an axis tick.

default `format`

`format`
ticksreadonly number[]

The values the axis marks. Default: four, evenly spaced from min to max.

–
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.

–
namestring

Also submits both ends with a native form, under this name.

–
disabledboolean–
Other props pass through: extends ComponentProps<'div'>.
MotionThumbs and range, data changes, first paint… · reduced motion

Motion

Motion tokens
MomentTokens
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.emphasized

340ms · 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.emphasized

340ms · 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.enter

140ms · 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 group named by label. Its thumbs are the Slider's: each is a slider named as the label's minimum and maximum ("Minimum file size"), or by thumbLabels, and speaks its value through format.
  • 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.