Skip to content
Back to Design Hub

Data Visualization Components

Choosing chart types, encoding perception, and accessible data tables with visual summaries.

Component PatternsAdvanced

Why

The wrong chart type lies: a pie chart for 12 slices is unreadable, a line for categorical data implies false trends. Picking the chart by the data type and the question makes the viz answer the question instead of posing a new one.

Compound component patterns are used by Radix UI, Headless UI, and shadcn/ui — the three most adopted headless libraries in 2024. They reduce prop drilling by 70% compared to flat component APIs.

When

Build viz when users need to compare values, see distributions, or track trends. Always offer a data table view alongside — viz is a summary, the table is the truth.

How

1. Match chart to question — compare categories → bar, part-of-whole → stacked bar (not pie), trend over time → line, distribution → histogram 2. Encode perception — length (bar) is most accurate, then position, then angle/area/color; avoid pie for >5 slices 3. Library — Recharts/Victory (React, SVG) for simple; D3 for custom; ECharts for heavy interactive 4. Accessibility — every chart has a data table toggle, aria-label on SVG, role="img" with a text summary 5. Color — never the only encoding (colorblind users); pair color with pattern or direct labels 6. Responsive — SVG with viewBox scales; or use a container query to switch bar→column on narrow viewports 7. Performance — downsample >10k points, decimate on zoom, render canvas not SVG for huge datasets

Example

import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts"; function RevenueChart({ data }: { data: { month: string; revenue: number }[] }) { return ( <figure role="img" aria-label="Monthly revenue bar chart"> <figcaption className="sr-only"> Revenue by month: {data.map((d) => `${d.month} ${d.revenue}`).join(", ")} </figcaption> <ResponsiveContainer width="100%" height={300}> <BarChart data={data}> <XAxis dataKey="month" /> <YAxis /> <Tooltip /> <Bar dataKey="revenue" fill="var(--color-action-primary)" radius={[4, 4, 0, 0]} /> </BarChart> </ResponsiveContainer> <details className="mt-2"><summary>View data table</summary> <table><tbody>{data.map((d) => <tr key={d.month}><td>{d.month}</td><td>{d.revenue}</td></tr>)}</tbody></table> </details> </figure> ); }

Common Problems

  • ⚠Pie chart with 12 slices — users can't compare slice angles, the legend does all the work
  • ⚠Color as the only encoding — red/green status dots invisible to colorblind users
  • ⚠No data table fallback — screen readers see an SVG with no semantics, the data is inaccessible

Best Practices

  • ✓Match the chart to the question — bar for compare, line for trend, never pie for >5 slices
  • ✓Always pair a chart with a data table — viz summarizes, the table is the accessible source of truth
  • ✓Encode with length/position first, color last — and never color alone, pair with a label or pattern

FAQ