# Sparkline

A run of numbers at the size of a word.

- Group: Data
- Import: `import { Sparkline } from '@misoto22/folio/charts'`
- Page: https://ui.misoto22.com/components/sparkline/
- Related: line-chart, table

## When to reach for it

In a table cell, beside a figure, at the end of a row. When the trend needs reading precisely it wants a LineChart and its own space.

## Anatomy

- **Inline row** (required) — An inline-flex span, full width with 8px of gap, so the run sits in a table cell or beside a figure without breaking the line it is on.
- **Plot** (required) — One svg role="img" over a 0–100 by 0–100 viewBox with preserveAspectRatio="none", so it stretches to whatever width the container gives it. height, 28px by default, is the only fixed dimension.
- **Mark** (required) — The path, its area fill, or the bars — variant picks one. The stroke is drawn with non-scaling-stroke, which is what keeps the line the same weight in a narrow cell and a wide one after the box has been stretched to fit.
- **Last point** — A 2px dot on the final reading, from showLast, on the line and area variants. The bars variant carries the end of the run in its own last bar and draws no dot.
- **Printed value** — value, in mono tabular figures after the plot. It is also what the accessible name says after the label, and it is the only figure this component ever prints.
- **Too-short state** — What renders in place of the whole plot when fewer than two finite numbers survive: the label and “not enough data”, as one line of mono meta text.

## Best practices

### Do

- Pass value whenever a figure matters. The plot has no axis and no scale, so it carries shape and nothing else, and value is both the one number printed and the reading appended to the accessible name. Left out, that name falls back to the last point through toLocaleString — the raw number, without the unit, the currency or the rounding the row beside it uses.
- Pin domain across any two that will be read against each other. Each run is normalised into the same fixed box from its OWN min and max, so the highest point always touches the top edge and the lowest always the floor: a series moving between 4 and 6 and a series moving between 400 and 900 draw the same silhouette, and the difference between them is drawn nowhere.
- Downsample a long run before handing it over. The x step is 100 divided by one less than the number of points, spread across whatever width the cell has, so four hundred readings in a 200px cell land half a pixel apart and the path fills in as a band.
- Read a flat line through the middle as “unchanged”, not as “at its floor”. A run whose min equals its max has a scale with no width, so no position on it is truer than another and every point sits at the centre — the same answer Heatmap and BulletChart give a zero span, and the one that keeps “unchanged” and “pinned at its worst” apart in a column of them.

### Don’t

- Do not assume something chart-shaped always renders. Non-finite entries are filtered out first, and anything left under two points returns a line of text instead of an SVG, so the new account’s row is a sentence where every other row in the column is a chart.

## Accessibility

- label is required and is the whole accessible name: a sparkline has no axes and no legend, so nothing else describes it.
- Axis-less by design. Every piece of chrome that would let it answer “what value exactly” also makes it too big to sit inline, which was the only reason to reach for it.
- Pin domain for a column of them: on independent domains every row peaks and troughs identically, which is how a table of sparklines becomes actively misleading.
- One path, no rendering engine — so a hundred of them in a table cost nothing.

## Sparkline

A run of numbers at the size of a word — in a table cell, beside a figure, at the end of a row. Deliberately axis-less, gridless and label-less: a sparkline answers "what shape has this been", and every piece of chrome that would make it answer "what value exactly" also makes it too big to sit inline, which was the only reason to reach for it. When the exact value matters, print the number beside it — `value` does — and when the trend needs reading precisely, it wants a `<LineChart>` and its own space. No rendering engine: it is one `<path>` over a normalised viewBox, so it costs nothing to put a hundred of them in a table.

### Props

- `data` (required) — `number[]`. The numbers, in order. Anything shorter than two points draws nothing.
- `label` (required) — `string`. What the run is, in a sentence. Required, and it is the whole accessible name: a sparkline has no axes and no legend, so nothing else describes it.
- `variant` — `SparklineVariant` default `'line'`.
- `height` — `number` default `28`. Height in pixels. The width comes from the container.
- `domain` — `[number, number]`. The domain, as `[min, max]`. Derived from the data when omitted. Pin it whenever a column of sparklines is meant to be compared: on independent domains every row peaks and troughs identically, which is the one way a table of sparklines can be actively misleading.
- `showLast` — `boolean` default `true`. Marks the last point, which is usually the one being asked about.
- `value` — `ReactNode`. The value announced alongside the label — the current reading, formatted. Falls back to the last number.
- `className` — `string`.

## Example — variants

```tsx
import { Sparkline, type SparklineVariant } from '@misoto22/folio/charts'

<div className="flex w-full max-w-sm flex-col gap-5">
  {VARIANTS.map((variant) => (
    <div key={variant} className="flex items-center gap-4">
      <span className="w-12 shrink-0 mono-meta text-(--ink-3-aa)">{variant}</span>
      <Sparkline label={`Weekly signups, ${variant}`} data={data} variant={variant} value="61" />
    </div>
  ))}
</div>
```

## Example — in a table

```tsx
import { TBody, TD, TH, THead, TR, Table } from '@misoto22/folio'
import { Sparkline } from '@misoto22/folio/charts'

<Table caption="Visitors by channel, last seven weeks">
  <THead>
    <TR>
      <TH>Channel</TH>
      <TH>Trend</TH>
      <TH align="end">This week</TH>
    </TR>
  </THead>
  <TBody>
    {ROWS.map((row) => (
      <TR key={row.channel}>
        <TD>{row.channel}</TD>
        <TD>
          <Sparkline
            label={`${row.channel}, last seven weeks`}
            data={row.trend}
            domain={DOMAIN}
            height={22}
          />
        </TD>
        <TD align="end" className="font-mono tabular-nums">
          {row.now}
        </TD>
      </TR>
    ))}
  </TBody>
</Table>
```

## Example — not enough data

```tsx
import { Sparkline } from '@misoto22/folio/charts'

<div className="flex w-full max-w-sm flex-col gap-5">
  {ROWS.map((row) => (
    <div key={row.project} className="flex items-center gap-4">
      <span className="w-16 shrink-0 mono-meta text-(--ink-3-aa)">{row.project}</span>
      <Sparkline label={`${row.project}, last seven days`} data={row.trend} />
    </div>
  ))}
</div>
```
