Legend Item
- Stable
- WCAG 2.2 evidence
- RTL
One entry of a chart legend: the series' swatch and its name,
with an optional value. The swatch comes from seriesStyle(seriesIndex), so it has the colour AND the dash or marker shape
of the series in the plot (WCAG 1.4.1); the name is text, so nothing depends on colour. Two variants. **Static**: a plain span, presentational, renders in React Server Components; put it in a
list (ChartFrame wraps each legend item in an li). **Interactive** (onChange set): a React Aria ToggleButton that shows
or hides the series. It is a button with aria-pressed (pressed = shown): Tab reaches it, Enter and Space toggle it, keyboard
focus shows the focus ring, it is at least 24 px tall (WCAG 2.1.1, 2.4.7, 2.5.8, 4.1.2), and a hidden series is muted,
struck through and dimmed rather than only recoloured (1.4.1). Only the button part is client code. Consumer duties: use the same series wording as the data table; hide the series in the plot when onChange fires and keep
at least one series visible; tell users when hiding changes what a chart summary says.
import { LegendItem } from "@nexera-ui/react";- 8
- examples
- 16
- props
- 6
- live controls
- 1
- platform
- 10
- WCAG criteria
- 2
- blocks use 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 { LegendItem } from "@nexera-ui/react";
<LegendItem />Examples 7
The same examples as Storybook, rendered live. Open Code to copy one.
Series styles
All eight series in each swatch kind. Every series differs by glyph or dash as well as colour (WCAG 1.4.1); with hasMarker the line swatches show the glyph the plot draws on the line, which tells apart the series that share a dash.
import { LegendItem } from "@nexera-ui/react";
const shapes = ["dot", "line", "dashed", "square"] as const satisfies readonly SeriesMarkerShape[];
const series = Array.from({ length: 8 }, (_, index) => index);
export function SeriesStyles() {
return (
<div className="flex flex-col gap-4">
{shapes.map((shape) => (
<ul key={shape} className="m-0 flex list-none flex-wrap gap-4 p-0">
{series.map((seriesIndex) => (
<li key={seriesIndex}>
<LegendItem
seriesIndex={seriesIndex}
shape={shape}
label={`${shape} ${String(seriesIndex + 1)}`}
/>
</li>
))}
</ul>
))}
<ul className="m-0 flex list-none flex-wrap gap-4 p-0">
{series.map((seriesIndex) => (
<li key={seriesIndex}>
<LegendItem
seriesIndex={seriesIndex}
shape="line"
hasMarker
label={`line + marker ${String(seriesIndex + 1)}`}
/>
</li>
))}
</ul>
</div>
);
}
Interactive
The interactive variant (onChange set): a toggle button with aria-pressed. Tab to an entry and press Enter or Space; a hidden series is struck through and dimmed as well as muted. Hover, press and keyboard focus have their own states.
import { useState } from "react";
import { LegendItem } from "@nexera-ui/react";
const series = Array.from({ length: 8 }, (_, index) => index);
function ToggleLegend({ names, shape }: { names: readonly string[]; shape: SeriesMarkerShape }) {
const [hidden, setHidden] = useState<readonly number[]>([]);
return (
<div className="flex flex-col gap-3">
<ul className="m-0 flex list-none flex-wrap gap-4 p-0">
{names.map((name, index) => (
<li key={name}>
<LegendItem
seriesIndex={index}
shape={shape}
label={name}
isSelected={!hidden.includes(index)}
onChange={(shown) => {
setHidden((current) =>
shown ? current.filter((i) => i !== index) : [...current, index],
);
}}
/>
</li>
))}
</ul>
<p className="text-body-small text-secondary m-0">
{hidden.length === 0
? "All series shown."
: `Hidden: ${hidden.map((i) => names[i] ?? "").join(", ")}.`}
</p>
</div>
);
}
export function Interactive() {
return <ToggleLegend names={["Actual", "Plan", "Forecast"]} shape="line" />;
}
Interactive states
Interactive entries in every state: shown, hidden, disabled, and with a value.
import { LegendItem } from "@nexera-ui/react";
export function InteractiveStates() {
return (
<ul className="m-0 flex list-none flex-wrap gap-4 p-0">
<li>
<LegendItem label="Shown" onChange={() => undefined} />
</li>
<li>
<LegendItem
label="Hidden"
defaultSelected={false}
seriesIndex={1}
onChange={() => undefined}
/>
</li>
<li>
<LegendItem label="Disabled" isDisabled seriesIndex={2} onChange={() => undefined} />
</li>
<li>
<LegendItem label="With value" value="42 %" seriesIndex={3} onChange={() => undefined} />
</li>
</ul>
);
}
With values
An optional value after the label, for example a share or a total.
import { LegendItem } from "@nexera-ui/react";
export function WithValues() {
return (
<ul className="m-0 flex list-none flex-wrap gap-4 p-0">
{[
["Engineering", "42 %"],
["Sales", "31 %"],
["Operations", "27 %"],
].map(([label, value], index) => (
<li key={label}>
<LegendItem seriesIndex={index} shape="square" label={label} value={value} />
</li>
))}
</ul>
);
}
In chart frame
Inside a ChartFrame legend, which wraps each entry in a list item. Long names wrap instead of overflowing.
import { LegendItem } from "@nexera-ui/react";
export function InChartFrame() {
return (
<ChartFrame
title="Headcount by month"
description="Last 12 months"
summary="Headcount grew from 96 to 128 over twelve months."
legend={[
<LegendItem key="a" seriesIndex={0} shape="line" label="Actual headcount" />,
<LegendItem key="b" seriesIndex={1} shape="dashed" label="Plan" />,
<LegendItem
key="c"
seriesIndex={2}
shape="line"
label="Forecast including the annual company offsite and every open requisition"
/>,
]}
>
<svg role="img" aria-label="Placeholder plot" viewBox="0 0 100 20" className="h-20 w-full" />
</ChartFrame>
);
}
Long labels
A name longer than the room wraps inside its container (the swatch keeps its size).
import { LegendItem } from "@nexera-ui/react";
export function LongLabels() {
return (
<div className="flex max-w-xs flex-col gap-4">
<LegendItem
seriesIndex={1}
shape="line"
label="Annual compensation review budget for the whole engineering organisation"
/>
<LegendItem
seriesIndex={2}
shape="square"
onChange={() => undefined}
label="Annual compensation review budget for the whole engineering organisation"
/>
</div>
);
}
Right to left
Right-to-left: the swatch is on the right of the label; the toggle's padding and hover fill mirror.
import { LegendItem } from "@nexera-ui/react";
export function RightToLeft() {
return (
<ul className="m-0 flex list-none flex-wrap gap-4 p-0">
<li>
<LegendItem seriesIndex={0} shape="line" label="عدد الموظفين" />
</li>
<li>
<LegendItem seriesIndex={1} shape="dashed" label="الخطة" />
</li>
<li>
<LegendItem seriesIndex={2} label="التوقعات" onChange={() => undefined} />
</li>
</ul>
);
}
Props 16
Press "Try it" on a card to load that prop into the playground.
16 props shown
onChangeNexera(isSelected: boolean) => voidNot set: a static entry has no interaction. Pass onChange to make the entry a toggle.
Called with the new state when the entry is toggled: true = show the series, false = hide it. Setting it makes the
entry interactive; leave it out for a static entry.
isSelectedNexerabooleanOnly on an interactive entry (with onChange).
Whether the series is shown (controlled). A hidden series reads as muted, struck-through text with a dimmed marker.
Pair it with onChange.
defaultSelectedNexerabooleanOnly on an interactive entry (with onChange).
Whether the series is shown at first render (uncontrolled).
isDisabledNexerabooleanOnly on an interactive entry (with onChange).
Disables the toggle.
label*NexeraReactNodeName of the series. It is the accessible name of an interactive entry, so use the same wording as the table column and the tooltip. Long names wrap; they are never truncated.
seriesIndexNexeranumberZero-based index of the series. It picks the colour (chart-1 to chart-8) and the dash and marker glyph from
seriesStyle(index), so the entry matches the series in the plot and is not told by colour alone (WCAG 1.4.1).
shapeNexera"dot" | "line" | "dashed" | "square"Kind of swatch. Pick what the chart draws: line for line charts, square
for bars and areas, dot for points and slices, dashed for a line that is always dashed (a plan or target line).
line follows the series' own dash pattern, so it matches the plot for every series.
hasMarkerNexerabooleanDraw the series' marker glyph on the middle of a line or dashed swatch. Set it when
the plot draws markers on its line: eight dash patterns cannot tell eight series apart, a dash plus a glyph can (WCAG
1.4.1). Ignored for dot and square.
valueNexeraReactNodeValue after the label, for example a total ("128") or a share ("42 %"). Shown when provided. UNVERIFIED: not in the Figma legend item. Format numbers for the locale before passing them.
classNameNexerastringExtra classes for the root, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the root.
onPressReact Aria(e: PressEvent) => voidHandler that is called when the press is released over the target.
onPressStartReact Aria(e: PressEvent) => voidHandler that is called when a press interaction starts.
onPressEndReact Aria(e: PressEvent) => voidHandler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressChangeReact Aria(isPressed: boolean) => voidHandler that is called when the press state changes.
onPressUpReact Aria(e: PressEvent) => voidHandler that is called when a press is released over the target, regardless of whether it started on the target or not.
* 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
5 direct · 5 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · supporting test
- 1.4.10ReflowLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · 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:.
data-disableddata-hovereddata-presseddata-selected
<LegendItem className="data-disabled:opacity-90 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.