Skip to content
Brok UI

Loading…

No results

Sparkline

Open source

A compact inline SVG trend chart (line, area or bar) with an optional value, trend indicator and date range — no charting dependency.

Version
v1.3.2
Stability
stable
License
MIT
Related
Chart
Stat
Chart Card

Preview

560.3M +12.4% /30d
May 14 – Jun 12
Variant
Tone
previews.components.sparkline.default.blade.php Blade
@php
    $series = [12, 18, 14, 22, 26, 20, 28, 24, 33, 30, 38, 35, 44, 48];
@endphp

<div class="flex flex-wrap items-end gap-8">
    <x-ui.sparkline :data="$series" variant="line" :show-endpoint="true" />

    <x-ui.sparkline :data="$series" variant="line" tone="primary" :show-endpoint="true" />

    <x-ui.sparkline
        :data="$series"
        variant="area"
        tone="success"
        value="560.3M"
        label="/30d"
        :trend="12.4"
        date-range="May 14 – Jun 12"
    />

    <x-ui.sparkline :data="$series" variant="bar" tone="info" />
</div>

Variant options

Line Current
Area Current
Bar Current

Tone options

Neutral Current
Primary Current
Success Current
Destructive Current
Warning Current
Info Current
Muted Current
Foreground Current

Installation

terminal
php artisan ui:add sparkline

Registry contract

php artisan ui:add sparkline writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/sparkline.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

sparkline.md
# Brok UI: Sparkline (`sparkline`)

A compact inline SVG trend chart (line, area or bar) with an optional value, trend indicator and date range — no charting dependency.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add sparkline
```

## Usage

```blade
@php
    $series = [12, 18, 14, 22, 26, 20, 28, 24, 33, 30, 38, 35, 44, 48];
@endphp

<div class="flex flex-wrap items-end gap-8">
    <x-ui.sparkline :data="$series" variant="line" :show-endpoint="true" />

    <x-ui.sparkline :data="$series" variant="line" tone="primary" :show-endpoint="true" />

    <x-ui.sparkline
        :data="$series"
        variant="area"
        tone="success"
        value="560.3M"
        label="/30d"
        :trend="12.4"
        date-range="May 14 – Jun 12"
    />

    <x-ui.sparkline :data="$series" variant="bar" tone="info" />
</div>
```

## Props

- `data` (array, default `[]`) — The series: a list of numbers, or rows with a `value` key.
- `variant` (line|area|bar, default `line`) — Line, filled area or bars.
- `width` (int, default `120`) — Drawing width in pixels.
- `height` (int, default `36`) — Drawing height in pixels.
- `strokeWidth` (int, default `2`) — Line width in pixels (line and area).
- `tone` (neutral|primary|success|destructive|warning|info|muted|foreground, default `neutral`) — Token colour of the drawing: neutral (the foreground colour, so primary stays reserved for the main action and focus), primary (the brand colour, opt in), a status tone, muted (muted-foreground) or foreground. An unknown tone falls back to neutral.
- `showEndpoint` (bool, default `false`) — Marks the last point with a dot.
- `showBaseline` (bool, default `false`) — Draws a dashed line at the lowest value.
- `label` (string|null, default `null`) — What the series plots. Printed beside the value and part of the SVG accessible name (role="img").
- `labelHidden` (bool, default `false`) — Keeps the label out of the visible header while it still names the SVG, for a sparkline in a table cell or under a heading that already says what it plots.
- `value` (mixed|null, default `null`) — The current value, printed before the trend.
- `trend` (mixed|null, default `null`) — A percentage change, printed with an up or down arrow in the success or destructive colour.
- `dateRange` (mixed|null, default `null`) — The period the series covers, printed under the drawing.

## Use when

- Use when trend, distribution, flow, intensity, or relationship questions are easier to answer visually than in raw numbers.
- Showing the shape of a trend beside a number, in a table row, stat or card.
- Giving a metric context without the weight of a full chart.

## Avoid when

- Do not use a chart when users mainly need exact lookup or when a single metric would be clearer as text or a stat.
- A reader needs to read values off the chart; chart gives axes, a legend and tooltips.
- Plotting more than one series, which a sparkline cannot separate.

## Anti-patterns

- Decorative charts without a meaningful question

## Rules

- Use the `<brok:sparkline>` tag (or `<x-ui.sparkline>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/sparkline
- Registry JSON (files, props, contract): https://brokui.dev/r/open/sparkline.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Examples

label-hidden.blade.php Blade
{{-- label-hidden: the label names the drawing for assistive technology without being printed, as in a table cell whose column header already says what it plots. --}}
<table class="w-full max-w-md text-sm">
    <thead>
        <tr class="border-b border-border text-start text-muted-foreground">
            <th scope="col" class="py-2 text-start font-medium">{{ __('Project') }}</th>
            <th scope="col" class="py-2 text-start font-medium">{{ __('Issues closed, last 8 weeks') }}</th>
        </tr>
    </thead>
    <tbody>
        <tr class="border-b border-border">
            <td class="py-2">{{ __('Billing migration') }}</td>
            <td class="py-2">
                <x-ui.sparkline :data="[4, 6, 5, 9, 8, 11, 10, 14]" tone="foreground" :label="__('Billing migration: issues closed per week')" label-hidden />
            </td>
        </tr>
        <tr>
            <td class="py-2">{{ __('Mobile onboarding') }}</td>
            <td class="py-2">
                <x-ui.sparkline :data="[9, 7, 8, 6, 5, 6, 4, 3]" tone="muted" :label="__('Mobile onboarding: issues closed per week')" label-hidden />
            </td>
        </tr>
    </tbody>
</table>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
data array [] The series: a list of numbers, or rows with a `value` key.
variant line | area | bar line Line, filled area or bars.
width int 120 Drawing width in pixels.
height int 36 Drawing height in pixels.
strokeWidth int 2 Line width in pixels (line and area).
tone neutral | primary | success | destructive | warning | info | muted | foreground neutral Token colour of the drawing: neutral (the foreground colour, so primary stays reserved for the main action and focus), primary (the brand colour, opt in), a status tone, muted (muted-foreground) or foreground. An unknown tone falls back to neutral.
showEndpoint bool false Marks the last point with a dot.
showBaseline bool false Draws a dashed line at the lowest value.
label string | null null What the series plots. Printed beside the value and part of the SVG accessible name (role="img").
labelHidden bool false Keeps the label out of the visible header while it still names the SVG, for a sparkline in a table cell or under a heading that already says what it plots.
value mixed | null null The current value, printed before the trend.
trend mixed | null null A percentage change, printed with an up or down arrow in the success or destructive colour.
dateRange mixed | null null The period the series covers, printed under the drawing.

Slots

  • default — Unused; the sparkline draws from the data prop only.

Data slots

Stable hooks for CSS overrides and browser tests.

sparkline

Behavior

  • variant renders the same data as a line, a filled area or bars, sized by width and height rather than by its container.
  • showEndpoint marks the last point and showBaseline draws the zero line; tone picks the token colour.
  • The drawing is neutral (the foreground colour) by default; tone="primary" opts in to the brand colour. data-tone on the root reports the tone.
  • The SVG is drawn server-side, so there is no charting dependency and no layout shift on load.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Chart and visual analytics

Reveal a visual trend, distribution, flow, or relationship.

Use when

  • Use when trend, distribution, flow, intensity, or relationship questions are easier to answer visually than in raw numbers.
  • Showing the shape of a trend beside a number, in a table row, stat or card.
  • Giving a metric context without the weight of a full chart.

Avoid when

  • Do not use a chart when users mainly need exact lookup or when a single metric would be clearer as text or a stat.
  • A reader needs to read values off the chart; chart gives axes, a legend and tooltips.
  • Plotting more than one series, which a sparkline cannot separate.

Use instead

  • Table for exact lookup
  • Stat for a single value

Anti-patterns

  • Decorative charts without a meaningful question
Anatomy
root
Theming hooks
sparkline

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The chart is role="img" with an accessible name summarising the series, so it is announced rather than skipped.
  • With `label-hidden` the label is not printed and is not duplicated: it reaches assistive technology once, as the SVG's aria-label (with the value in front when one is set). Without a label the name is the generic "Trend sparkline", so pass one.
  • The value and trend are rendered as text beside the chart, so the trend does not depend on reading the drawing.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/sparkline.blade.php Blade
{{--
    Sparkline: a compact inline SVG trend.

    - label: printed beside the value and part of the SVG's accessible name.
    - labelHidden: keeps the label out of the visible header but still names
      the SVG (role="img" aria-label), for a sparkline in a table cell or next
      to a heading that already says what it plots.
    - tone: neutral (the default) draws in the foreground colour, so a
      product that keeps primary for its one main action and focus is not
      diluted; primary opts in to the brand colour.
--}}
@props([
    'data' => [],
    'variant' => 'line',
    'width' => 120,
    'height' => 36,
    'strokeWidth' => 2,
    'tone' => 'neutral',
    'showEndpoint' => false,
    'showBaseline' => false,
    'label' => null,
    'labelHidden' => false,
    'value' => null,
    'trend' => null,
    'dateRange' => null,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $variant = $styles['normalizeVariant']($variant);
    $variant = in_array($variant, ['line', 'area', 'bar'], true) ? $variant : 'line';

    // Tone resolves to a fixed text-* token class so the SVG paints with
    // currentColor — no inline colour and JIT-visible (never spliced).
    $tones = [
        'neutral' => 'text-foreground',
        'primary' => 'text-primary',
        'success' => 'text-success',
        'destructive' => 'text-destructive',
        'warning' => 'text-warning',
        'info' => 'text-info',
        'muted' => 'text-muted-foreground',
        'foreground' => 'text-foreground',
    ];
    $tone = isset($tones[$tone]) ? $tone : 'neutral';
    $toneClass = $tones[$tone];
    $labelHidden = filter_var($labelHidden, FILTER_VALIDATE_BOOLEAN);
    $showLabel = $label !== null && ! $labelHidden;

    // Normalise data to a flat list of floats.
    $points = array_values(array_map(
        fn ($d) => is_array($d) ? (float) ($d['value'] ?? 0) : (float) $d,
        $data,
    ));
    $n = count($points);

    $w = max(1, (int) $width);
    $h = max(1, (int) $height);
    $sw = (float) $strokeWidth;
    $pad = $sw + 1;

    $min = $n ? min($points) : 0.0;
    $max = $n ? max($points) : 0.0;
    $span = ($max - $min) ?: 1.0;

    $fx = fn (int $i) => $n <= 1 ? $w / 2 : $pad + ($i / ($n - 1)) * ($w - 2 * $pad);
    $fy = fn (float $v) => $h - $pad - (($v - $min) / $span) * ($h - 2 * $pad);
    $fmt = fn (float $f) => rtrim(rtrim(number_format($f, 2, '.', ''), '0'), '.');

    $linePath = '';
    foreach ($points as $i => $v) {
        $linePath .= ($i === 0 ? 'M' : 'L').$fmt($fx($i)).' '.$fmt($fy($v)).' ';
    }
    $linePath = trim($linePath);
    $areaPath = $n
        ? 'M'.$fmt($fx(0)).' '.$fmt($h - $pad).' '
            .implode('', array_map(fn ($i, $v) => 'L'.$fmt($fx($i)).' '.$fmt($fy($v)).' ', array_keys($points), $points))
            .'L'.$fmt($fx($n - 1)).' '.$fmt($h - $pad).' Z'
        : '';

    $barW = $n > 0 ? max(1.0, ($w - 2 * $pad) / max(1, $n) * 0.7) : 1.0;

    $trendValue = is_numeric($trend) ? (float) $trend : null;
    $trendUp = $trendValue !== null && $trendValue >= 0;

    $a11y = $label
        ? trim(($value ? $value.' ' : '').$label)
        : __('Trend sparkline');
@endphp

<div
    data-slot="sparkline"
    data-variant="{{ $variant }}"
    data-tone="{{ $tone }}"
    {{ $attributes->merge(['class' => 'inline-flex flex-col gap-1 '.$toneClass]) }}
>
    @if ($value !== null || $showLabel || $trendValue !== null)
        <div class="flex items-baseline gap-2 text-foreground">
            @if ($value !== null)<span class="text-sm font-semibold tabular-nums">{{ $value }}</span>@endif
            @if ($trendValue !== null)
                <span class="inline-flex items-center gap-0.5 text-xs font-medium tabular-nums {{ $trendUp ? 'text-success' : 'text-destructive' }}">
                    <svg class="size-3 {{ $trendUp ? '' : 'rotate-180' }}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7M12 5v14" /></svg>
                    {{ ($trendUp ? '+' : '').$fmt($trendValue) }}%
                </span>
            @endif
            @if ($showLabel)<span class="text-xs text-muted-foreground">{{ $label }}</span>@endif
        </div>
    @endif

    <svg
        viewBox="0 0 {{ $w }} {{ $h }}"
        width="{{ $w }}"
        height="{{ $h }}"
        class="overflow-visible"
        preserveAspectRatio="none"
        role="img"
        aria-label="{{ $a11y }}"
    >
        @if ($n > 0)
            @if ($variant === 'bar')
                @foreach ($points as $i => $v)
                    @php $bx = $fx($i) - $barW / 2; $by = $fy($v); @endphp
                    <rect x="{{ $fmt(max(0, $bx)) }}" y="{{ $fmt($by) }}" width="{{ $fmt($barW) }}" height="{{ $fmt(max(0.5, $h - $pad - $by)) }}" rx="1" fill="currentColor" />
                @endforeach
            @else
                @if ($variant === 'area')
                    <path d="{{ $areaPath }}" fill="currentColor" fill-opacity="0.12" stroke="none" />
                @endif
                <path d="{{ $linePath }}" fill="none" stroke="currentColor" stroke-width="{{ $sw }}" stroke-linecap="round" stroke-linejoin="round" />
                @if ($showBaseline)
                    <line x1="{{ $fmt($pad) }}" y1="{{ $fmt($fy($min)) }}" x2="{{ $fmt($w - $pad) }}" y2="{{ $fmt($fy($min)) }}" stroke="currentColor" stroke-opacity="0.2" stroke-width="1" stroke-dasharray="2 2" />
                @endif
                @if ($showEndpoint && $n > 0)
                    <circle cx="{{ $fmt($fx($n - 1)) }}" cy="{{ $fmt($fy($points[$n - 1])) }}" r="{{ $fmt($sw + 0.5) }}" fill="currentColor" />
                @endif
            @endif
        @endif
    </svg>

    @if ($dateRange !== null)
        <span class="text-xs text-muted-foreground">{{ $dateRange }}</span>
    @endif
</div>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.3.2
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0