Charts
Lollipop Chart
A categorical comparison that pairs a thin value stem with a prominent endpoint.
When to use it: Use it for ranked comparisons where bars feel too heavy and the endpoint deserves emphasis.
Example
import {
ChartDescription,
ChartTable,
ChartTitle,
ChartTooltip,
LollipopChart,
LollipopChartLollipop,
LollipopChartPlot,
} from "@comp0/react";
const adoption = [
{ label: "Email", value: 82 },
{ label: "Calendar", value: 64 },
{ label: "Files", value: 48 },
{ label: "Chat", value: 35 },
] as const;
const formatPercent = (value: number) => `${value}%`;
export function Example() {
return (
<LollipopChart
values={adoption}
categoryLabel="Feature"
valueLabel="Adoption"
formatValue={formatPercent}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-teal-600 dark:has-[:focus-visible]:outline-teal-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Feature adoption
</ChartTitle>
<LollipopChartPlot
aria-label="Lollipop chart comparing feature adoption"
xMin={0}
xMax={100}
className="mx-auto mt-5 aspect-square w-full max-w-md overflow-visible"
>
{(lollipop) => (
<LollipopChartLollipop lollipop={lollipop} className="group outline-none">
<line
x1={lollipop.baseline}
x2={lollipop.x}
y1={lollipop.y}
y2={lollipop.y}
className="stroke-teal-600 group-data-active:stroke-zinc-950 dark:stroke-teal-400 dark:group-data-active:stroke-white"
strokeWidth="3"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
<circle
cx={lollipop.x}
cy={lollipop.y}
r="3"
className="fill-teal-700 stroke-white dark:fill-teal-300 dark:stroke-zinc-950"
strokeWidth="1.5"
vectorEffect="non-scaling-stroke"
/>
</LollipopChartLollipop>
)}
</LollipopChartPlot>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Email is the most adopted feature, while chat has the most room to grow.
</ChartDescription>
<ChartTable className="mt-4 w-full border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800">
<caption className="sr-only">Feature adoption percentages</caption>
<thead>
<tr>
<th scope="col">Feature</th>
<th scope="col">Adoption</th>
</tr>
</thead>
<tbody>
{adoption.map((item) => (
<tr key={item.label}>
<th scope="row">{item.label}</th>
<td>{formatPercent(item.value)}</td>
</tr>
))}
</tbody>
</ChartTable>
<ChartTooltip className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950" />
</LollipopChart>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
LollipopChart
Native figure sharing categorical values, labels, and formatting. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
LollipopChartPlot / LollipopChartLollipop
Horizontal stems and endpoint circles against visible axes. Owns a DOM element.
ChartDescription
Visible prose summarizing the comparison. Owns a DOM element.
ChartTable
Native table with exact category values. Owns a DOM element.
ChartTooltipOptional
Optional floating value label shown on hover or focus. Owns a DOM element.
Step by step
- 1
Add the main part
Start LollipopChart with labelled values and visible category and value axis labels.
- 2
Add the supporting parts
Render each endpoint with LollipopChartLollipop so the value and category share one keyboard-reachable mark.
- 3
Make the behavior clear
Keep the baseline and category labels visible, and include the exact values in a native table.
Exampletsx <LollipopChart values={adoption} categoryLabel="Feature" valueLabel="Adoption"> <ChartTitle>Feature adoption</ChartTitle> <LollipopChartPlot aria-label="Lollipop chart comparing feature adoption"> {(lollipop) => ( <LollipopChartLollipop lollipop={lollipop}> <circle cx={lollipop.x} cy={lollipop.y} r="2" /> </LollipopChartLollipop> )} </LollipopChartPlot> <ChartTable> <caption>Feature adoption</caption> </ChartTable> <ChartTooltip /> </LollipopChart>;
Keyboard
- ⇥
- Enters the chart at its current lollipop and leaves with one more Tab.
- ↓
- Moves to the next lollipop without wrapping.
- ↑
- Moves to the previous lollipop without wrapping.
- Home
- Moves to the first lollipop.
- End
- Moves to the last lollipop.
- Esc
- Dismisses an open ChartTooltip.
Forms and accessibility
Charts are descriptive content and do not create form values.
Accessibility checklist
- Keep category and value axis labels visible and format ticks with the same units as the table.
- Wrap each endpoint in LollipopChartLollipop so its category and value are keyboard reachable.
- Keep the stem and endpoint visible in every color scheme; do not encode ranking through color alone.
- Include exact values in a native table and use ChartTooltip only as an enhancement.
API reference
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, LollipopChart, LollipopChartLollipop, LollipopChartPlot } from "@comp0/react";LollipopChart
DOM elementNative figure sharing categorical values, labels, and formatting.
| Prop | Type | Description |
|---|---|---|
values | readonly CategoricalChartValue[] | Category labels and finite numeric values. |
categoryLabel | string | Visible headings for category and numeric axes. |
valueLabel | string | Visible headings for category and numeric axes. |
formatValue | (value: number) => string | Formats numeric ticks and table cells. |
ChartTitle
DOM elementNative figcaption that visibly names the figure.
LollipopChartPlot / LollipopChartLollipop
DOM elementHorizontal stems and endpoint circles against visible axes.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the comparison. |
xMin | number | Optional numeric bounds and tick count. |
xMax | number | Optional numeric bounds and tick count. |
xTickCount | number | Optional numeric bounds and tick count. |
children | (lollipop: LollipopChartLollipopState) => ReactNode | Custom endpoint renderer. |
lollipop | LollipopChartLollipopState | Endpoint state passed to the mark. |
ChartDescription
DOM elementVisible prose summarizing the comparison.
ChartTable
DOM elementNative table with exact category values.
ChartTooltip
OptionalDOM elementOptional floating value label shown on hover or focus.
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
LollipopChartPlot / LollipopChartLollipop
| Style hook | Meaning |
|---|---|
[data-value] | The numeric value represented by the endpoint. |
[data-active] | The endpoint currently reached by pointer or keyboard. |
ChartTooltip
| Style hook | Meaning |
|---|---|
[data-open] | A value tooltip is visible. |