Histogram Chart
- Stable
- WCAG 2.2 evidence
- RTL
A histogram: how often values fall in each range. Give raw values and the chart bins them
(bins, binWidth or thresholds), or give bins that are already counted. Title, range switch, KPI and text alternative are the
shared card; the plot is laid out for the width of its container and renders on the server from the same props. Accessibility: the figure is named by title and described by summary; the data table has every bin with its range, both edges,
its count and its share, so the binning is stated and not only drawn (WCAG 1.1.1, 1.3.1). The plot is ONE tab stop: Left and Right
move between bins, Home and End jump to the ends, Page Up and Page Down skip a tenth; a bin is announced with its range and count.
Hover or focus shows a tooltip that stays while hovered and closes with Escape (1.4.13, 2.1.1). Bin labels are ranges in the locale's
digits and thin out or rotate on a narrow container (1.4.10); the plot stays left-to-right in right-to-left pages. The draw-in is
behind motion-safe:. The bars are one series, so colour does not carry a distinction (1.4.1); the median and other markers carry
their own text. Counting: a bin includes its start and excludes its end; the last bin of raw data includes its end. Bars are equal in width whatever
the bin width in data units, so uneven thresholds show counts per bin, not densities. valueFormat, formatValue and
formatAxisValue format the measured values (edges, markers), counts always use the locale's number format. The bin labels on the
axis use the full format of an edge (a compact "0.1" for both 0.05 and 0.1 would be wrong) unless you give formatAxisValue. Consumer duties: a title that asks the question, a summary with the main message, the unit of the x axis, saying what a marker is.
import { HistogramChart } from "@nexera-ui/react";- 12
- examples
- 24
- props
- 2
- live controls
- 1
- platform
- 8
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { HistogramChart } from "@nexera-ui/react";
<HistogramChart />Examples 11
The same examples as Storybook, rendered live. Open Code to copy one.
Bin width
Raw values, one-year bins (binWidth): the chart counts them and labels the bins as ranges ("0–1", "1–2").
import { HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const TENURES = FIGMA_COUNTS.flatMap((count, bin) =>
Array.from({ length: count }, (_, i) => bin + (i + 0.5) / count),
);
<HistogramChart
values={TENURES}
binWidth={1}
markers={[
{ value: medianOf(TENURES) ?? 0, label: `Median ${(medianOf(TENURES) ?? 0).toFixed(1)} yrs` },
]}
/>;
Thresholds
thresholds are the edges between bins: the first bin is open below the first edge ("<1") and the last open above the last ("8+").
import { HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const TENURES = FIGMA_COUNTS.flatMap((count, bin) =>
Array.from({ length: count }, (_, i) => bin + (i + 0.5) / count),
);
<HistogramChart values={TENURES} thresholds={[1, 2, 3, 4, 5, 6, 7, 8]} />;
Bin count
bins as a number asks for about that many bins; the edges are rounded to nice numbers.
import { HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const TENURES = FIGMA_COUNTS.flatMap((count, bin) =>
Array.from({ length: count }, (_, i) => bin + (i + 0.5) / count),
);
<HistogramChart bins={5} values={TENURES} markers={[]} />;
Formatted values
Percent values with valueFormat: the edges are formatted as percentages in the locale, the counts as plain numbers.
import { HistogramChart } from "@nexera-ui/react";
<HistogramChart
title="Engagement scores"
description="Survey responses, 5 % bins"
summary="Most responses score between 60 % and 80 %."
values={[
0.42, 0.55, 0.58, 0.61, 0.63, 0.66, 0.68, 0.7, 0.71, 0.72, 0.74, 0.77, 0.79, 0.83, 0.88, 0.95,
]}
binWidth={0.1}
valueFormat={{ style: "percent" }}
valueLabel="Responses"
xAxisLabel="Engagement score"
markers={[]}
/>;
Loading
import { HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const FIGMA_LABELS = ["<1", "1", "2", "3", "4", "5", "6", "7", "8+"];
const countedBins = FIGMA_COUNTS.map((count, index) => ({
start: index === 0 ? null : index,
end: index === 8 ? null : index + 1,
count,
label: FIGMA_LABELS[index] ?? "",
}));
<HistogramChart bins={countedBins} state={{ status: "loading" }} />;
Empty
import { HistogramChart } from "@nexera-ui/react";
<HistogramChart
values={[]}
markers={[]}
state={{
status: "empty",
title: "No employees in this period",
description: "Try a longer period or clear the department filter.",
}}
/>;
Error state
import { Button, HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const FIGMA_LABELS = ["<1", "1", "2", "3", "4", "5", "6", "7", "8+"];
const countedBins = FIGMA_COUNTS.map((count, index) => ({
start: index === 0 ? null : index,
end: index === 8 ? null : index + 1,
count,
label: FIGMA_LABELS[index] ?? "",
}));
<HistogramChart
bins={countedBins}
state={{
status: "error",
title: "Couldn't load tenure",
description: "Check your connection and try again.",
action: (
<Button variant="secondary" size="sm">
Try again
</Button>
),
}}
/>;
Single point
One value is one bin with one bar.
import { HistogramChart } from "@nexera-ui/react";
<HistogramChart
title="Tenure of the new team"
description="One employee"
summary="One employee, 4.2 years at the company."
values={[4.2]}
valueLabel="Employees"
markers={[]}
/>;
Many points
5000 values in about 120 bins: range labels thin out to what fits and the keys still reach every bin (Page Down skips 12).
import { HistogramChart } from "@nexera-ui/react";
const MANY = Array.from({ length: 5000 }, (_, i) => {
const a = ((i * 7919) % 1000) / 1000;
const b = ((i * 104729) % 1000) / 1000;
return Math.round((a + b + (i % 7) / 7) * 40);
});
<HistogramChart
title="Response times"
description="5000 support tickets, minutes to first reply"
summary="Most tickets get a first reply within two hours."
values={MANY}
binWidth={1}
valueLabel="Tickets"
xAxisLabel="Minutes to first reply"
markers={[]}
/>;
Long labels
Long title, description, axis title and range labels: the header wraps, the labels rotate or thin out, nothing is cut off.
import { HistogramChart } from "@nexera-ui/react";
<HistogramChart
title="Time spent in the hiring process from first application to signed offer across all departments and locations"
description="Calendar days between the first application and the signed offer, grouped in ten-day bins"
summary="Most hires take between thirty and fifty days."
bins={Array.from({ length: 9 }, (_, i) => ({
start: i * 10,
end: i * 10 + 10,
count: [4, 12, 30, 41, 26, 14, 8, 3, 2][i] ?? 0,
}))}
valueLabel="Hires completed in this period"
xAxisLabel="Calendar days between the first application and the signed offer"
markers={[{ value: 38, label: "Median 38 days" }]}
/>;
Right to left
Right-to-left page: the card, header and table mirror, the plot keeps its left-to-right axis, digits and labels follow the locale.
import { HistogramChart } from "@nexera-ui/react";
const FIGMA_COUNTS = [24, 28, 22, 16, 12, 9, 7, 5, 5];
const FIGMA_LABELS = ["<1", "1", "2", "3", "4", "5", "6", "7", "8+"];
const countedBins = FIGMA_COUNTS.map((count, index) => ({
start: index === 0 ? null : index,
end: index === 8 ? null : index + 1,
count,
label: FIGMA_LABELS[index] ?? "",
}));
<HistogramChart
title="توزيع مدة الخدمة"
description="١٢٨ موظفًا في فئات من سنة واحدة"
summary="أكبر مجموعة من ٢٨ موظفًا أمضت بين سنة وسنتين في الشركة. الوسيط ٢٫٤ سنة."
bins={countedBins}
valueLabel="الموظفون"
xAxisLabel="سنوات العمل في الشركة"
markers={[{ value: 2.4, label: "الوسيط ٢٫٤ سنة" }]}
labels={{ share: "النسبة", from: "من", to: "إلى" }}
table={{ categoryLabel: "الفئة", label: "عرض البيانات كجدول" }}
/>;
Props 24
Press "Try it" on a card to load that prop into the playground.
24 props shown
valuesNexerareadonly number[]The raw measurements to count into bins (tenure in years of every employee). Values that are not finite numbers are ignored.
Give values, or already counted bins, not both: counted bins win.
binsNexeranumber | readonly HistogramBinData[]How to bin. A number is about how many bins to make from values (the edges are rounded to nice numbers, so the count can differ
a little); an array is data that is already counted, one entry per bin ({ start, end, count, label? }, null for an open end).
Without it and without binWidth and thresholds, the bin count follows Sturges' rule (log2 of the number of values plus one, at
most 30).
binWidthNexeranumberWidth of every bin in the unit of values (1 for one-year bins). The edges are multiples of it. Wins over a bins number.
thresholdsNexerareadonly number[]The edges between bins. [1, 2, 3, 4, 5, 6, 7, 8] makes the open-ended bins "<1" and "8+" and seven bins between. Wins over
binWidth and bins.
valueLabelNexerastringName of what is counted (Employees): the legend-free series name in the tooltip, the point labels and the table column.
Translate it.
xAxisLabelNexerastringTitle of the x axis, under the bin labels. Label the unit.
markersNexerareadonly HistogramMarkerInput[]Vertical markers with a label. The line is decorative: the label is also in the plot's name.
styleIndexNexeranumberIndex of the series style (colour chart-1..8). Figma colours the bars chart-2.
labelsNexeraPartial<HistogramLabels>Texts of the table and the tooltip that the chart invents. Translate them.
title*NexerastringChart title: the question the chart answers ("Headcount"). Required: it names the figure, the plot and the data table (WCAG 1.1.1).
descriptionNexeraReactNodeSecondary line under the title.
summary*NexerastringOne or two sentences with what the chart shows and its main message ("Headcount grew from 96 to 128 over twelve months"). Required: it describes the figure for assistive technology (WCAG 1.1.1); the data table has the details.
rangeNexeraChartRangePropsRange switch in the header: 3M, 6M, 12M. Pass the data of the chosen range back in.
actionsNexeraReactNodeExtra controls in the header, after the range switch.
kpiNexeraChartKpiPropsKPI row above the plot: the headline number with an optional label or change badge.
stateNexeraChartPlotStateThe state that replaces the plot: loading (skeleton), empty or error. A chart without a value to plot shows the empty state
by itself; pass { status: "empty", title, description, action } to give it your wording.
tableNexeraChartTableOptionsThe data table that is the text alternative: caption, first-column header and whether it is visible or only for assistive technology.
localeNexerastringLocale of digits and separators.
valueFormatNexeraNumberFormatOptionsIntl.NumberFormat options for values ({ style: "currency", currency: "PKR" }, { style: "percent" }). Serialisable, so it
works from a Server Component; formatValue needs a Client Component.
formatValueNexera(value: number) => stringFormats a value for tooltips, point labels and the table (full precision). Wins over valueFormat.
formatAxisValueNexera(value: number) => stringFormats an axis tick (short: "29M").
heightNexeranumberHeight of the plot in px. The card grows with the header, KPI and legend around it.
plotLabelNexerastringAccessible name of the plot group. Translate it.
noDataLabelNexerastringText for a missing value in the tooltip and the table. Translate it.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
7 direct · 1 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.10ReflowLevel AA · tested directly
- 1.4.13Content on Hover or FocusLevel AA · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<HistogramChart className="shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.