Sparkline
betaA tiny trend line, inline with text or in a table cell. Plain SVG, with no Recharts.
Preview
tone
area
size
in text
Revenue grew this quarter and reached $23.9K a week.
in a table
| Metric | Last 9 weeks | Now |
|---|---|---|
| Revenue | $23.9K | |
| Churn | 5.6% | |
| Latency | 201 ms |
loading
one value
empty
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/sparkline
Use it for
- Showing the shape of a trend next to a number, in a stat card, or in a table row.
- Name it with aria-label, like "Revenue, last 30 days". Screen readers hear the name with the first and last values.
- Use tone for meaning: success for a good trend, danger for a bad one, neutral when it is neither.
- Pass the numbers oldest first.
Avoid it for
- When people need to read exact values or dates. Use LineChart, which has axes and a tooltip.
- Comparing two or more series. Use LineChart.
- As the only place a number appears. Show the current value in text next to it.
Props
- data
The numbers to draw, oldest first. Values that are not finite numbers are skipped.
number[]
- aria-label
Required. Names the trend. Screen readers hear it with the first and last values.
string
- tone
The line's color role.
"neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"
- size
Height and width, from --control-h-sm, so density scales it. Small fits a line of text.
"sm" | "md" | "lg" · default "md"
- area
Fills the space under the line with the tone at 15%.
boolean · default false
- loading
Shows a skeleton at the same size.
boolean · default false
- valueFormat
Formats the first and last values that screen readers hear.
Intl.NumberFormatOptions
- locale
The locale for those numbers.
string · default "en-US"
- texture
dither draws the area as dots, gradient fades it from the top, and grain speckles it. The line glows on dark pages. Hover fills gradient and grain in. Left out, the line follows the Texture around it.
"flat" | "dither" | "gradient" | "grain"
- className
Extra classes. Later classes win, so className='w-full' stretches it.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| data | number[] | - | The numbers to draw, oldest first. Values that are not finite numbers are skipped. |
| aria-label | string | - | Required. Names the trend. Screen readers hear it with the first and last values. |
| tone | "neutral" | "brand" | "danger" | "success" | "warning" | "neutral" | The line's color role. |
| size | "sm" | "md" | "lg" | "md" | Height and width, from --control-h-sm, so density scales it. Small fits a line of text. |
| area | boolean | false | Fills the space under the line with the tone at 15%. |
| loading | boolean | false | Shows a skeleton at the same size. |
| valueFormat | Intl.NumberFormatOptions | - | Formats the first and last values that screen readers hear. |
| locale | string | "en-US" | The locale for those numbers. |
| texture | "flat" | "dither" | "gradient" | "grain" | - | dither draws the area as dots, gradient fades it from the top, and grain speckles it. The line glows on dark pages. Hover fills gradient and grain in. Left out, the line follows the Texture around it. |
| className | string | - | Extra classes. Later classes win, so className='w-full' stretches it. |
Accessibility
- Renders a <span role="img">. Its name is your aria-label plus the first and last values, like "Revenue: 12.4 to 23.9". TypeScript requires aria-label.
- The words it adds, "to", "loading", and "no data", are English.
- The SVG inside is hidden from screen readers, so they hear the name once.
- The line takes the tone's solid color, which the theme tests hold to 3:1 against the page and cards, in light and dark.
- It has no tab stop, because it has nothing to interact with. Put exact values in text nearby.
- It draws on the server and never animates, so its space and shape are in the static HTML.
Examples
Next to a number
<p className="flex items-center gap-2">
$23.9K
<Sparkline data={[12.4, 14.1, 13.2, 16.8, 18.7, 23.9]} tone="success" aria-label="Revenue, last 6 weeks" />
</p><p className="flex items-center gap-2">
$23.9K
<Sparkline texture="dither" data={[12.4, 14.1, 13.2, 16.8, 18.7, 23.9]} tone="success" aria-label="Revenue, last 6 weeks" />
</p><p className="flex items-center gap-2">
$23.9K
<Sparkline texture="gradient" data={[12.4, 14.1, 13.2, 16.8, 18.7, 23.9]} tone="success" aria-label="Revenue, last 6 weeks" />
</p><p className="flex items-center gap-2">
$23.9K
<Sparkline texture="grain" data={[12.4, 14.1, 13.2, 16.8, 18.7, 23.9]} tone="success" aria-label="Revenue, last 6 weeks" />
</p>In a table cell, filled
<td>
<Sparkline data={row.history} area tone="brand" aria-label={`${row.name}, last 30 days`} />
</td><td>
<Sparkline texture="dither" data={row.history} area tone="brand" aria-label={`${row.name}, last 30 days`} />
</td><td>
<Sparkline texture="gradient" data={row.history} area tone="brand" aria-label={`${row.name}, last 30 days`} />
</td><td>
<Sparkline texture="grain" data={row.history} area tone="brand" aria-label={`${row.name}, last 30 days`} />
</td>While it loads
<Sparkline data={data ?? []} loading={isLoading} aria-label="Revenue, last 6 weeks" /><Sparkline texture="dither" data={data ?? []} loading={isLoading} aria-label="Revenue, last 6 weeks" /><Sparkline texture="gradient" data={data ?? []} loading={isLoading} aria-label="Revenue, last 6 weeks" /><Sparkline texture="grain" data={data ?? []} loading={isLoading} aria-label="Revenue, last 6 weeks" />A grain area that fills in on hover
<Sparkline area tone="success" data={[4, 6, 5, 8, 7, 9]} texture="grain" aria-label="Revenue, last 6 weeks" />