DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Chart

Low-level charting primitives — ChartContainer, ChartTooltip, ChartLegend, ChartMessage, ChartSkeleton and a typed ChartConfig — that wrap Recharts v3 with Tessina tokens. Compose them with raw Recharts, or use the ready-made typed charts.

Tessina charts are thin, token-driven primitives wrapped around Recharts v3, with the Recharts composition left fully exposed. Reach for these when you need a chart shape beyond the six ready-made components (Area, Line, Bar, Pie, Radar, Radial); otherwise use those.

Playground

Installation

pnpm add @tessinaui/ui recharts

Usage

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  ChartLegend,
  ChartLegendContent,
  type ChartConfig,
} from "@tessinaui/ui";
import { CartesianGrid, Line, LineChart, XAxis } from "recharts";

const config: ChartConfig = {
  desktop: { label: "Desktop", color: "var(--chart-1)" },
  mobile: { label: "Mobile", color: "var(--chart-2)" },
};

<ChartContainer
  config={config}
  className="aspect-[2/1]"
  label="Mobile overtook desktop in April"
>
  <LineChart data={data} accessibilityLayer>
    <CartesianGrid vertical={false} />
    <XAxis dataKey="month" tickLine={false} axisLine={false} />
    <ChartTooltip content={<ChartTooltipContent />} />
    <Line dataKey="desktop" stroke="var(--color-desktop)" />
    <Line dataKey="mobile" stroke="var(--color-mobile)" />
    <ChartLegend content={<ChartLegendContent />} />
  </LineChart>
</ChartContainer>

ChartContainer reads config and injects each series colour as a --color-<key> CSS variable scoped to the chart, so raw Recharts children reference var(--color-desktop) in stroke / fill / stopColor. Because the underlying --chart-N tokens flip with the .dark class, charts recolour on theme change with no React re-render. Colour values are sanitised before injection — a value that could escape the style declaration (;, braces, url(...)) is dropped with a dev warning; var(), hsl(), oklch() and calc() compositions all pass.

Examples

Default

A single-series area chart composed from ChartContainer and raw Recharts primitives, named as a figure via label.

Accessibility

label names the chart (role="figure") — state the insight, not the axes. description adds a visually hidden summary, because a static read must carry the message without requiring interaction. The chart itself is keyboard-operable.

Tooltip Indicators

ChartTooltipContent supports three indicator styles — dot, line, and dashed.

Legend

A multi-series chart with a token-styled ChartLegend labelling each series. The legend wraps on narrow screens instead of overflowing the plot.

Interactive legend

onItemClick turns legend items into real toggle buttons (aria-pressed, keyboard-operable); inactiveKeys dims the toggled ones; the series' own hide prop does the hiding. State stays in your component — the legend is controlled.

Legend with values

The mobile-analytics staple: the legend doubles as the summary list, one row per series with the value right-aligned. A composition over the same config, not a prop.

Reference lines and bands

Goal lines, target ranges and a current-period highlight via Recharts ReferenceLine / ReferenceArea — chrome stays on tokens.

Comparison and projection

Prior period and projections never rely on hue alone: lighter dashed strokes for the previous range, a dashed continuation for the forecast, and legend swatches that mirror each line's style.

Value labels

Direct labels on marks via LabelList, with token-driven fill.

Axis formatting

tickFormatter for currency (durations and qualitative scales are the same move), plus values crossing zero with an explicit baseline.

Stat header

The universal dashboard-card anatomy: KPI number + delta badge + time-range switcher above the plot. Pure composition with house parts.

Small multiples

One mini chart per metric, no axes, a title per card — the grid stays legible without interaction.

Data table fallback

A toggleable table of the plotted rows. Primer treats "view as data table" as a near-default chart action, and it is the WCAG escape hatch when a series colour sits below the 3:1 marks floor.

Loading

ChartSkeleton renders a shape-aware loading placeholder per chart type — a loading pie looks like a pie. label localises the announcement.

Empty & Error

ChartMessage covers empty and error states. By default it replaces the chart (assistive tech should never traverse an empty shell behind a message); overlay floats a pill over decorative, aria-hidden chrome instead. It takes the §1 intent vocabulary. For a rich empty state with illustration and actions, compose EmptyState.

Primitives

ExportPurpose
ChartContainerResponsive wrapper; injects --color-<key> vars, token-styles axes/grid/cursor, draws the keyboard focus ring.
ChartConfigTyped map of series key → { label, icon?, color | theme: { light, dark } }.
ChartTooltip / ChartTooltipContentRecharts Tooltip + token-styled content (dot | line | dashed), polite live region.
ChartLegend / ChartLegendContentRecharts Legend + token-styled content; optional controlled series toggling.
ChartSkeletonShape-aware loading placeholder (area/line/bar/pie/donut/radial/radar).
ChartMessageEmpty / error placeholder with intent and overlay.
useChartAccess the active ChartConfig from a custom child.
useReducedMotionprefers-reduced-motion hook used to gate animation.

ChartContainer

PropTypeDefaultNotes
configChartConfig—required
labelstring—Accessible name; renders role="figure". State the insight.
descriptionstring—Visually hidden summary for assistive tech.
initialDimension{ width, height }{ 320, 200 }Pre-measurement size — no 0×0 SSR flash.
debouncenumber0Debounce (ms) for resize handling.

ChartTooltipContent

indicator ("dot" \| "line" \| "dashed"), hideLabel, hideIndicator, nameKey, labelKey, labelFormatter, formatter, labelClassName, color, and locale (BCP-47, for the default toLocaleString number formatting). Arbitrary data-*/aria-* reach the DOM; the props Recharts injects when cloning content are stripped by a closed list.

Native Recharts Tooltip props pass through ChartTooltip untouched — notably trigger="click" (touch-friendly pinning), defaultIndex, shared, and portal for escaping clipped or scrolling containers.

ChartLegendContent

verticalAlign ("top" \| "bottom" — "middle" is deprecated and renders as bottom with a dev warning), hideIcon, nameKey, onItemClick(key), inactiveKeys. With onItemClick set, items are <button aria-pressed> toggles; without it they stay inert. portal and the item handlers on the Recharts Legend itself also pass through.

ChartMessage

intent (none \| error \| warning \| success \| info — ink uses the *-tinted-foreground tokens so every pairing stays AA on the wash) and overlay (centered pill over aria-hidden chrome instead of a replacing box).

ChartSkeleton

variant, orientation, ticks, showLegend, legendItems, barRounded (mirrors bar-chart's rounded scale), label (default "Loading chart").

Accessibility

Recharts v3 enables its accessibilityLayer by default: the chart's <svg> is a single tab stop (tabIndex=0, role="application").

KeyAction
TabFocus the chart (one stop — data points are not individual tab stops)
← / →Step the active tooltip through the data (RTL-aware, no wrap)
EnterPin / unpin the tooltip
EscDismiss the visible tooltip

There is no Home/End or series switching upstream. The visible focus ring is drawn by ChartContainer (Recharts ships no focus styling; Safari would show nothing at all). ChartTooltipContent is a polite live region, so keyboard stepping announces values — Recharts' own live region only exists in its default tooltip and is assertive.

AT matrix: VoiceOver verified locally — note VoiceOver users must toggle QuickNav off or the arrow keys are consumed before the chart sees them (upstream limitation, documented by Recharts). NVDA and JAWS are UNTESTED on this machine and not claimed.

Colour: series tokens are measured against both themes — every --chart-N/theme pair clears the 3:1 marks floor (WCAG 1.4.11; light amber was stepped from 2.17:1 to 3.30:1 in this release). Never encode by colour alone: pair hue with dash pattern, shape or direct labels (see Comparison), and offer the data-table fallback for anything colour cannot carry. Axis tick text rides muted-foreground (4.5:1 text floor); gridlines are decorative and exempt.

Static readability: the chart must carry its message with zero interaction — that is what label/description are for. Density guardrails worth honouring (Primer's numbers): ≤5 line series, ≤10 bars, ≤5 slices; beyond that, split the chart or fold into "Other". Don't interpolate across data gaps — render the gap.

Agent notes

Stable hooks for automation: data-slot="chart" (container, plus data-chart="<id>"), chart-tooltip, chart-legend, chart-legend-item (buttons when interactive, aria-pressed reflects visibility), chart-message, chart-skeleton (aria-busy="true" while loading). Chart state is deterministic: a hidden series is hide on the Recharts element plus the key in inactiveKeys.

Migration

WasNow
ChartLegendContent verticalAlign="middle"Deprecated — type is "top" | "bottom"; runtime renders bottom + dev warning
Light --chart-3 hsl(36 100% 49.7%)hsl(36 100% 40%) — clears the 3:1 marks floor; dark theme unchanged

Theming

Every colour resolves to a token — series via --chart-1 … --chart-6, and chrome via border (grid/axis), muted-foreground (ticks/legend), and popover (tooltip). Nothing is hardcoded. The six series tokens are brand-led and contrast-measured in both light and dark (ratios recorded in the TES-36 ledger).

CarouselChatBubble

On this page

PlaygroundInstallationUsageExamplesDefaultAccessibilityTooltip IndicatorsLegendInteractive legendLegend with valuesReference lines and bandsComparison and projectionValue labelsAxis formattingStat headerSmall multiplesData table fallbackLoadingEmpty & ErrorPrimitivesChartContainerChartTooltipContentChartLegendContentChartMessageChartSkeletonAccessibilityAgent notesMigrationTheming