Skip to content
Brok UI

Loading…

No results

Charts

Open source

Token-themed data visualisations — the core multi-series chart plus dependency-free SVG variants for sankey, word-cloud, venn and violin plots.

Version
v1.1.1
Stability
stable
License
MIT
Related
Chart
Chart Tooltip
Sankey Chart
Word Cloud
Venn Diagram
Violin Chart
3 of 7

Chart Legend — A static legend for any chart: swatch, label and optional value per series, as a wrapping row or a stack, with circle, square or line markers, an optional delta badge, and a large lg headline size.

Preview

Revenue
$8,750.00 up12%
Expense
$1,852.00 down3%
Revenue
Costs
Profit
p50
p95
p99
Blade
62%
JavaScript
21%
CSS
11%
Orientation
previews.components.chart-legend.default.blade.php Blade
<div class="flex flex-col items-center gap-6">
    {{-- The panel-header headline: a marker + label row, then a large value
         with its delta badge — the key above a "Total revenue" chart. Expense
         is a dashed series with tone="positive" on its delta, so a falling
         expense still reads as good news even though the arrow points down. --}}
    <x-ui.chart-legend size="lg" :items="[
        ['label' => __('Revenue'), 'color' => 'chart-1', 'value' => '$8,750.00', 'delta' => 12],
        ['label' => __('Expense'), 'color' => 'chart-2', 'dashed' => true, 'value' => '$1,852.00', 'delta' => -3, 'tone' => 'positive'],
    ]" marker="line" />
    <x-ui.chart-legend :items="[['label' => __('Revenue')], ['label' => __('Costs')], ['label' => __('Profit')]]" />
    <x-ui.chart-legend marker="line" :items="[['label' => 'p50', 'color' => 'chart-1'], ['label' => 'p95', 'color' => 'chart-2'], ['label' => 'p99', 'color' => 'chart-3']]" />
    <x-ui.chart-legend orientation="vertical" marker="square" :items="[
        ['label' => __('Blade'), 'value' => '62%', 'color' => 'chart-1'],
        ['label' => __('JavaScript'), 'value' => '21%', 'color' => 'chart-2'],
        ['label' => __('CSS'), 'value' => '11%', 'color' => 'chart-3'],
    ]" />
</div>
Horizontal Current
Vertical Current

Installation

terminal
php artisan ui:add chart-legend

Registry contract

php artisan ui:add chart-legend 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/chart-legend.blade.php
Registry dependencies
badge
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.

chart-legend.md
# Brok UI: Chart Legend (`chart-legend`)

A static legend for any chart: swatch, label and optional value per series, as a wrapping row or a stack, with circle, square or line markers, an optional delta badge, and a large lg headline size.

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

## Install

```bash
php artisan ui:add chart-legend
```

## Usage

```blade
<div class="flex flex-col items-center gap-6">
    {{-- The panel-header headline: a marker + label row, then a large value
         with its delta badge — the key above a "Total revenue" chart. Expense
         is a dashed series with tone="positive" on its delta, so a falling
         expense still reads as good news even though the arrow points down. --}}
    <x-ui.chart-legend size="lg" :items="[
        ['label' => __('Revenue'), 'color' => 'chart-1', 'value' => '$8,750.00', 'delta' => 12],
        ['label' => __('Expense'), 'color' => 'chart-2', 'dashed' => true, 'value' => '$1,852.00', 'delta' => -3, 'tone' => 'positive'],
    ]" marker="line" />
    <x-ui.chart-legend :items="[['label' => __('Revenue')], ['label' => __('Costs')], ['label' => __('Profit')]]" />
    <x-ui.chart-legend marker="line" :items="[['label' => 'p50', 'color' => 'chart-1'], ['label' => 'p95', 'color' => 'chart-2'], ['label' => 'p99', 'color' => 'chart-3']]" />
    <x-ui.chart-legend orientation="vertical" marker="square" :items="[
        ['label' => __('Blade'), 'value' => '62%', 'color' => 'chart-1'],
        ['label' => __('JavaScript'), 'value' => '21%', 'color' => 'chart-2'],
        ['label' => __('CSS'), 'value' => '11%', 'color' => 'chart-3'],
    ]" />
</div>
```

## Props

- `items` (array, default `[]`) — Rows to render: each an object with label, an optional color (a chart-N token or class), an optional value, an optional delta (number or preformatted string), an optional tone override (positive|negative|neutral) for the delta badge, and an optional dashed flag (line marker only).
- `orientation` (string, default `horizontal`) — Lays rows out as a wrapping row or a vertical stack.
- `marker` (string, default `circle`) — Swatch shape: circle, square, or line (a short dash, for line charts). A series can request a dashed stroke on the line marker with its own dashed flag.
- `label` (string, default `Legend`) — Accessible name for the legend region.
- `size` (default|lg, default `default`) — default renders the compact swatch/label/value row. lg renders the panel-header headline: a marker + label row, then the value in a large tabular-number style with its delta badge beside it.

## Use when

- Use when trend, distribution, flow, intensity, or relationship questions are easier to answer visually than in raw numbers.
- Labelling series colours for an SVG chart, or for an ApexCharts chart rendered with its built-in legend turned off.
- Needing the legend values to read as label/value pairs, such as beside a pie or donut 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.
- The chart already renders ApexCharts' own legend; do not add a second one.
- Only a single tooltip is needed at the pointer, not a static key; use chart-tooltip instead.

## Anti-patterns

- Decorative charts without a meaningful question

## Rules

- Use the `<brok:chart-legend>` tag (or `<x-ui.chart-legend>`) 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/chart-legend
- Registry JSON (files, props, contract): https://brokui.dev/r/open/chart-legend.json

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

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
items array [] Rows to render: each an object with label, an optional color (a chart-N token or class), an optional value, an optional delta (number or preformatted string), an optional tone override (positive|negative|neutral) for the delta badge, and an optional dashed flag (line marker only).
orientation string horizontal Lays rows out as a wrapping row or a vertical stack.
marker string circle Swatch shape: circle, square, or line (a short dash, for line charts). A series can request a dashed stroke on the line marker with its own dashed flag.
label string Legend Accessible name for the legend region.
size default | lg default default renders the compact swatch/label/value row. lg renders the panel-header headline: a marker + label row, then the value in a large tabular-number style with its delta badge beside it.

Slots

  • default — Unused; rows render from the items prop only.

Data slots

Stable hooks for CSS overrides and browser tests.

chart-legend chart-legend-item

Behavior

  • Renders as a description list, so each swatch/label pairs with its value the way a <dt>/<dd> pair does.
  • A row with no explicit color cycles through the shared chart palette by its index.
  • A row with a delta renders a small soft badge (composing badge's tone read-model) with an up/down glyph: the direction always reflects the delta's numeric sign, and the badge's tone defaults from that sign (positive up, negative down) unless the row's own tone overrides it — so a series such as an expense can mark a fall as good news without flipping the arrow.
  • size="lg" restructures each row into a headline block: the marker/label pair on top, the value in a larger tabular-number style with its delta badge beside it underneath.
  • 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.
  • Labelling series colours for an SVG chart, or for an ApexCharts chart rendered with its built-in legend turned off.
  • Needing the legend values to read as label/value pairs, such as beside a pie or donut 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.
  • The chart already renders ApexCharts' own legend; do not add a second one.
  • Only a single tooltip is needed at the pointer, not a static key; use chart-tooltip instead.

Use instead

  • Table for exact lookup
  • Stat for a single value

Anti-patterns

  • Decorative charts without a meaningful question
Anatomy
root chart-legend-item
Theming hooks
chart

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 root carries aria-label from the label prop, so screen readers announce the legend as a named region.
  • Each swatch is aria-hidden, including the dashed line marker; the series name is still readable as plain text beside it.
  • The delta badge's arrow glyph is aria-hidden with a translatable sr-only word (up/down/flat) read before the value, e.g. "up 12%".
  • 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/chart-legend.blade.php Blade
@props([
    // Items: each ['label' => string, 'color' => 'chart-1'|token class, 'value' => string|null,
    // 'delta' => number|string|null, 'tone' => 'positive'|'negative'|'neutral'|null, 'dashed' => bool].
    'items' => [],
    // horizontal (a wrapping row) | vertical (a stack).
    'orientation' => 'horizontal',
    // circle | square | line (a short dash, for line charts).
    'marker' => 'circle',
    'label' => 'Legend',
    // default | lg (variant). lg is the headline layout: a marker + label row,
    // then the value in a large tabular-number style with its delta badge
    // beside it — the panel-header key above a chart (see stat, chart-card).
    'size' => 'default',
])

@php
    // Shared with chart-tooltip and partition-bar.
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $swatches = $styles['chart']['swatches'];
    $strokes = $styles['chart']['strokes'];
    $chartPalette = $styles['chart']['palette'];
    $orientation = $orientation === 'vertical' ? 'vertical' : 'horizontal';
    $size = $size === 'lg' ? 'lg' : 'default';

    $rootLayout = match (true) {
        $size === 'lg' && $orientation === 'vertical' => 'flex flex-col gap-6',
        $size === 'lg' => 'flex flex-wrap items-start gap-x-8 gap-y-4',
        $orientation === 'vertical' => 'flex flex-col gap-1',
        default => 'flex flex-wrap items-center gap-x-4 gap-y-1',
    };
@endphp

{{-- A static legend for any chart — the SVG charts, or an ApexCharts chart with
     `legend.show` off. Rows are a description list so the values read as pairs.
     `size="lg"` turns each row into the panel-header headline: marker + label
     on top, a large value with its delta badge underneath. --}}
<dl
    data-slot="chart-legend"
    data-orientation="{{ $orientation }}"
    data-size="{{ $size }}"
    aria-label="{{ $label }}"
    {{ $attributes->merge(['class' => 'text-xs '.$rootLayout]) }}
>
    @foreach ($items as $i => $item)
        @php
            $color = $item['color'] ?? $chartPalette[$i % count($chartPalette)];
            // `dashed` only reads on the line marker — a dashed stroke reads
            // as a marker shape change on circle/square, not a dash pattern.
            $dashed = $marker === 'line' && (bool) ($item['dashed'] ?? false);
            $markerClass = match ($marker) {
                'square' => 'size-3 rounded-sm',
                'line' => $dashed ? 'h-0 w-4 border-t-2 border-dashed' : 'h-0.5 w-3 rounded-full',
                default => 'size-2 rounded-full',
            };
            $markerColorClass = $dashed
                ? ($strokes[$color] ?? 'border-current')
                : ($swatches[$color] ?? $item['color'] ?? '');

            // `delta`: a number (its sign picks the direction) or a
            // preformatted string (a leading -/− reads the same way, then is
            // dropped — the badge's arrow already carries the direction).
            // `tone` lets a series override the sign-derived colour, so an
            // expense series can mark a fall as good news without flipping
            // the arrow that shows what actually happened.
            $delta = $item['delta'] ?? null;
            $deltaText = null;
            $deltaTone = null;
            $deltaDirection = null;

            if ($delta !== null && $delta !== '') {
                $isNumeric = is_numeric($delta);
                $deltaString = $isNumeric ? '' : trim((string) $delta);
                $isNegative = $isNumeric
                    ? ((float) $delta) < 0
                    : (str_starts_with($deltaString, '-') || str_starts_with($deltaString, '−'));
                $isZero = $isNumeric && (float) $delta === 0.0;

                $deltaDirection = $isZero ? 'flat' : ($isNegative ? 'down' : 'up');
                $deltaTone = in_array($item['tone'] ?? null, ['positive', 'negative', 'neutral'], true)
                    ? $item['tone']
                    : match ($deltaDirection) {
                        'up' => 'positive',
                        'down' => 'negative',
                        default => 'neutral',
                    };
                $deltaText = $isNumeric
                    ? rtrim(rtrim(number_format(abs((float) $delta), 1), '0'), '.').'%'
                    : ltrim($deltaString, '+-−');

                // Fixed, hand-authored glyphs (never user data) — safe to
                // print unescaped so the badge stays a single reusable line
                // in both the default and lg layouts below.
                $deltaGlyph = match ($deltaDirection) {
                    'up' => '<svg viewBox="0 0 20 20" fill="currentColor" class="size-3" aria-hidden="true"><path fill-rule="evenodd" d="M10 17a.75.75 0 0 1-.75-.75V5.612L5.29 9.77a.75.75 0 0 1-1.08-1.04l5.25-5.5a.75.75 0 0 1 1.08 0l5.25 5.5a.75.75 0 1 1-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0 1 10 17Z" clip-rule="evenodd" /></svg>',
                    'down' => '<svg viewBox="0 0 20 20" fill="currentColor" class="size-3" aria-hidden="true"><path fill-rule="evenodd" d="M10 3a.75.75 0 0 1 .75.75v10.638l3.96-4.158a.75.75 0 1 1 1.08 1.04l-5.25 5.5a.75.75 0 0 1-1.08 0l-5.25-5.5a.75.75 0 1 1 1.08-1.04l3.96 4.158V3.75A.75.75 0 0 1 10 3Z" clip-rule="evenodd" /></svg>',
                    default => '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="size-3" aria-hidden="true"><path d="M4 10h12" /></svg>',
                };
                $deltaWord = match ($deltaDirection) {
                    'up' => __('up'),
                    'down' => __('down'),
                    default => __('flat'),
                };
            }
        @endphp
        @if ($size === 'lg')
            <div data-slot="chart-legend-item" class="flex flex-col gap-1">
                <dt class="flex items-center gap-2">
                    <span aria-hidden="true" class="shrink-0 {{ $markerClass }} {{ $markerColorClass }}"></span>
                    <span class="text-muted-foreground">{{ $item['label'] ?? '' }}</span>
                </dt>
                @if (isset($item['value']))
                    <dd class="flex items-center gap-2">
                        <span class="text-2xl font-semibold tabular-nums text-foreground">{{ $item['value'] }}</span>
                        @if ($deltaText !== null)
                            <x-ui.badge size="sm" :tone="$deltaTone" class="gap-1">{!! $deltaGlyph !!}<span class="sr-only">{{ $deltaWord }}</span>{{ $deltaText }}</x-ui.badge>
                        @endif
                    </dd>
                @endif
            </div>
        @else
            <div data-slot="chart-legend-item" class="flex items-center gap-2">
                <dt class="flex items-center gap-2">
                    <span aria-hidden="true" class="shrink-0 {{ $markerClass }} {{ $markerColorClass }}"></span>
                    <span class="text-muted-foreground">{{ $item['label'] ?? '' }}</span>
                </dt>
                @if (isset($item['value']))
                    <dd class="font-medium tabular-nums text-foreground">{{ $item['value'] }}</dd>
                @endif
                @if ($deltaText !== null)
                    <x-ui.badge size="sm" :tone="$deltaTone" class="gap-1">{!! $deltaGlyph !!}<span class="sr-only">{{ $deltaWord }}</span>{{ $deltaText }}</x-ui.badge>
                @endif
            </div>
        @endif
    @endforeach
</dl>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.1
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