Metrics
Dashboard analytics cards — a traffic trend card with a range control, a KPI performance row with sparklines, a sales-by-channel distribution, a weekly-traffic heatmap and a spending breakdown with a segmented bar and table.
Preview
@props([
'title' => null,
'total' => null,
'delta' => null,
'categories' => null,
'actionHref' => null,
])
{{--
Metrics Breakdown — a "Spending breakdown" style panel: a tray frame
holding a headline stat (total + delta), a thin segmented bar of the
categories with a dot/label/value legend, and a small table listing
each category with a tinted round icon, its share, and its amount.
`categories` rows: {name, percent, amount, icon?, tone?}. `percent` and
`amount` should add up to the headline `total`. `icon` selects a built-in
glyph (home, utensils, car, shopping, health — falls back to a plain
dot). `tone` pins a category to a chart token (chart-1..5, primary, info,
success, warning, destructive) instead of the auto-assigned palette
order; the same colour drives the bar segment, the legend dot, and the
table row's icon tint.
--}}
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$title ??= __('Spending breakdown');
$total ??= '$4,250';
$delta ??= '+8.4%';
$actionHref ??= '#';
$categories ??= [
['name' => __('Housing'), 'percent' => 45, 'amount' => '$1,912', 'icon' => 'home'],
['name' => __('Food & Dining'), 'percent' => 30, 'amount' => '$1,275', 'icon' => 'utensils'],
['name' => __('Transport'), 'percent' => 25, 'amount' => '$1,063', 'icon' => 'car'],
];
// Fixed inline icon set for the category chips, keyed by the `icon` field.
// Hand-authored, trusted markup — not user data — so it is safe to print
// unescaped below.
$icons = [
'home' => '<path d="M3 9.5 12 3l9 6.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-6h6v6"/>',
'utensils' => '<path d="M7 3v7a2 2 0 0 0 4 0V3"/><path d="M9 12v9"/><path d="M17 3c-1.1 0-2 1.57-2 4.5S15.9 12 17 12s2-1.57 2-4.5S18.1 3 17 3Z"/><path d="M17 12v9"/>',
'car' => '<path d="M3 17V11l2-5h14l2 5v6"/><path d="M3 11h18"/><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>',
'shopping' => '<path d="m6 2-3 4v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
'health' => '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>',
];
// Same palette order partition-bar assigns by default, so an unspecified
// category still lands on chart-1, chart-2, … in row order; `tone` opts a
// category into a fixed token instead.
$chartPalette = $styles['chart']['palette'];
$tints = [
'chart-1' => 'bg-chart-1/10 text-chart-1',
'chart-2' => 'bg-chart-2/10 text-chart-2',
'chart-3' => 'bg-chart-3/10 text-chart-3',
'chart-4' => 'bg-chart-4/10 text-chart-4',
'chart-5' => 'bg-chart-5/10 text-chart-5',
'primary' => 'bg-primary/10 text-primary',
'info' => 'bg-info/10 text-info',
'success' => 'bg-success/10 text-success-text',
'warning' => 'bg-warning/10 text-warning-text',
'destructive' => 'bg-destructive/10 text-destructive-text',
];
$rows = [];
foreach (array_values($categories) as $i => $cat) {
$variant = array_key_exists($cat['tone'] ?? null, $tints) ? $cat['tone'] : $chartPalette[$i % count($chartPalette)];
$rows[] = [
'name' => $cat['name'] ?? '',
'percent' => (float) ($cat['percent'] ?? 0),
'amount' => $cat['amount'] ?? null,
'iconKey' => $cat['icon'] ?? null,
'variant' => $variant,
];
}
$segments = array_map(fn ($row) => [
'label' => $row['name'],
'value' => rtrim(rtrim(number_format($row['percent'], 1), '0'), '.').'%',
'num' => $row['percent'],
'variant' => $row['variant'],
], $rows);
@endphp
<x-ui.tray
:title="$title"
:action-label="__('View all')"
:action-href="$actionHref"
{{ $attributes->merge(['class' => 'w-full']) }}
>
<x-slot:icon>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 3v18h18" />
<path d="M8 17V9" />
<path d="M13 17V5" />
<path d="M18 17v-7" />
</svg>
</x-slot:icon>
<div class="flex flex-col gap-6">
<x-ui.stat variant="plain" inverse :value="$total" :delta="$delta" />
<x-ui.partition-bar :segments="$segments" size="xs" />
<x-ui.table>
<x-ui.table.header>
<x-ui.table.row>
<x-ui.table.head>{{ __('Category') }}</x-ui.table.head>
<x-ui.table.head align="end">{{ __('Percent') }}</x-ui.table.head>
<x-ui.table.head align="end">{{ __('Total') }}</x-ui.table.head>
</x-ui.table.row>
</x-ui.table.header>
<x-ui.table.body>
@foreach ($rows as $row)
<x-ui.table.row>
<x-ui.table.cell>
<div class="flex min-w-0 items-center gap-2">
<span aria-hidden="true" class="inline-flex size-8 shrink-0 items-center justify-center rounded-full {{ $tints[$row['variant']] ?? $tints['primary'] }}">
@if ($row['iconKey'] !== null && isset($icons[$row['iconKey']]))
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">{!! $icons[$row['iconKey']] !!}</svg>
@else
<span class="size-2 rounded-full bg-current"></span>
@endif
</span>
<span class="min-w-0 truncate text-sm font-medium text-foreground">{{ $row['name'] }}</span>
</div>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="tabular-nums text-muted-foreground">{{ rtrim(rtrim(number_format($row['percent'], 1), '0'), '.') }}%</x-ui.table.cell>
<x-ui.table.cell align="end" class="font-medium tabular-nums text-foreground">{{ $row['amount'] }}</x-ui.table.cell>
</x-ui.table.row>
@endforeach
</x-ui.table.body>
</x-ui.table>
</div>
</x-ui.tray>
Installation
php artisan ui:add blocks/metrics-breakdown
Registry contract
Install confidence
php artisan ui:add blocks/metrics-breakdown
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.0.1
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/metrics-breakdown.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: Metrics Breakdown (`metrics-breakdown`)
A spending-breakdown panel: a tray frame holding a headline total with a delta, a segmented bar of categories with a dot/label legend, and a small table listing each category's share and amount.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/metrics-breakdown
```
## Render it
```blade
<x-blocks.metrics-breakdown />
```
## Props
- `title` (string, default `Spending breakdown`) — Tray header title.
- `total` (string, default `$4,250`) — Headline total shown as the stat value.
- `delta` (string, default `+8.4%`) — Change shown next to the total; the stat is toned negative because a spending increase is bad news regardless of the arithmetic sign.
- `categories` (mixed|null, default `null`) — Rows of {name, percent, amount, icon?, tone?}; null falls back to a 3-category sample (Housing 45% $1,912, Food & Dining 30% $1,275, Transport 25% $1,063). icon selects a built-in glyph (home, utensils, car, shopping, health); tone pins the row to a chart token instead of the auto-assigned palette order.
- `action-href` (string, default `#`) — Href for the tray's 'View all' action.
## Use when
- Summarising a total (spend, storage, time) across a handful of categories with a headline delta, a proportional segmented bar, and a per-category table of share and amount.
- Framing a KPI + chart + table trio inside one tinted panel with a header action such as 'View all'.
## Avoid when
- Only the channel list with per-row bars is needed, with no headline stat or segmented bar; use metrics-channels instead.
- The breakdown needs a time-series trend rather than a static category split; use metrics-traffic or metrics-kpis.
## Rules
- Render the installed block with `<x-blocks.metrics-breakdown />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/metrics-breakdown
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/metrics-breakdown.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Summarising a total (spend, storage, time) across a handful of categories with a headline delta, a proportional segmented bar, and a per-category table of share and amount.
- Framing a KPI + chart + table trio inside one tinted panel with a header action such as 'View all'.
Avoid when
- Only the channel list with per-row bars is needed, with no headline stat or segmented bar; use metrics-channels instead.
- The breakdown needs a time-series trend rather than a static category split; use metrics-traffic or metrics-kpis.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
- Anatomy
Usage
Render the block as a component, passing data where useful:
<x-blocks.metrics-breakdown />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'title' => null,
'total' => null,
'delta' => null,
'categories' => null,
'actionHref' => null,
])
{{--
Metrics Breakdown — a "Spending breakdown" style panel: a tray frame
holding a headline stat (total + delta), a thin segmented bar of the
categories with a dot/label/value legend, and a small table listing
each category with a tinted round icon, its share, and its amount.
`categories` rows: {name, percent, amount, icon?, tone?}. `percent` and
`amount` should add up to the headline `total`. `icon` selects a built-in
glyph (home, utensils, car, shopping, health — falls back to a plain
dot). `tone` pins a category to a chart token (chart-1..5, primary, info,
success, warning, destructive) instead of the auto-assigned palette
order; the same colour drives the bar segment, the legend dot, and the
table row's icon tint.
--}}
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$title ??= __('Spending breakdown');
$total ??= '$4,250';
$delta ??= '+8.4%';
$actionHref ??= '#';
$categories ??= [
['name' => __('Housing'), 'percent' => 45, 'amount' => '$1,912', 'icon' => 'home'],
['name' => __('Food & Dining'), 'percent' => 30, 'amount' => '$1,275', 'icon' => 'utensils'],
['name' => __('Transport'), 'percent' => 25, 'amount' => '$1,063', 'icon' => 'car'],
];
// Fixed inline icon set for the category chips, keyed by the `icon` field.
// Hand-authored, trusted markup — not user data — so it is safe to print
// unescaped below.
$icons = [
'home' => '<path d="M3 9.5 12 3l9 6.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-6h6v6"/>',
'utensils' => '<path d="M7 3v7a2 2 0 0 0 4 0V3"/><path d="M9 12v9"/><path d="M17 3c-1.1 0-2 1.57-2 4.5S15.9 12 17 12s2-1.57 2-4.5S18.1 3 17 3Z"/><path d="M17 12v9"/>',
'car' => '<path d="M3 17V11l2-5h14l2 5v6"/><path d="M3 11h18"/><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>',
'shopping' => '<path d="m6 2-3 4v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
'health' => '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>',
];
// Same palette order partition-bar assigns by default, so an unspecified
// category still lands on chart-1, chart-2, … in row order; `tone` opts a
// category into a fixed token instead.
$chartPalette = $styles['chart']['palette'];
$tints = [
'chart-1' => 'bg-chart-1/10 text-chart-1',
'chart-2' => 'bg-chart-2/10 text-chart-2',
'chart-3' => 'bg-chart-3/10 text-chart-3',
'chart-4' => 'bg-chart-4/10 text-chart-4',
'chart-5' => 'bg-chart-5/10 text-chart-5',
'primary' => 'bg-primary/10 text-primary',
'info' => 'bg-info/10 text-info',
'success' => 'bg-success/10 text-success-text',
'warning' => 'bg-warning/10 text-warning-text',
'destructive' => 'bg-destructive/10 text-destructive-text',
];
$rows = [];
foreach (array_values($categories) as $i => $cat) {
$variant = array_key_exists($cat['tone'] ?? null, $tints) ? $cat['tone'] : $chartPalette[$i % count($chartPalette)];
$rows[] = [
'name' => $cat['name'] ?? '',
'percent' => (float) ($cat['percent'] ?? 0),
'amount' => $cat['amount'] ?? null,
'iconKey' => $cat['icon'] ?? null,
'variant' => $variant,
];
}
$segments = array_map(fn ($row) => [
'label' => $row['name'],
'value' => rtrim(rtrim(number_format($row['percent'], 1), '0'), '.').'%',
'num' => $row['percent'],
'variant' => $row['variant'],
], $rows);
@endphp
<x-ui.tray
:title="$title"
:action-label="__('View all')"
:action-href="$actionHref"
{{ $attributes->merge(['class' => 'w-full']) }}
>
<x-slot:icon>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 3v18h18" />
<path d="M8 17V9" />
<path d="M13 17V5" />
<path d="M18 17v-7" />
</svg>
</x-slot:icon>
<div class="flex flex-col gap-6">
<x-ui.stat variant="plain" inverse :value="$total" :delta="$delta" />
<x-ui.partition-bar :segments="$segments" size="xs" />
<x-ui.table>
<x-ui.table.header>
<x-ui.table.row>
<x-ui.table.head>{{ __('Category') }}</x-ui.table.head>
<x-ui.table.head align="end">{{ __('Percent') }}</x-ui.table.head>
<x-ui.table.head align="end">{{ __('Total') }}</x-ui.table.head>
</x-ui.table.row>
</x-ui.table.header>
<x-ui.table.body>
@foreach ($rows as $row)
<x-ui.table.row>
<x-ui.table.cell>
<div class="flex min-w-0 items-center gap-2">
<span aria-hidden="true" class="inline-flex size-8 shrink-0 items-center justify-center rounded-full {{ $tints[$row['variant']] ?? $tints['primary'] }}">
@if ($row['iconKey'] !== null && isset($icons[$row['iconKey']]))
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">{!! $icons[$row['iconKey']] !!}</svg>
@else
<span class="size-2 rounded-full bg-current"></span>
@endif
</span>
<span class="min-w-0 truncate text-sm font-medium text-foreground">{{ $row['name'] }}</span>
</div>
</x-ui.table.cell>
<x-ui.table.cell align="end" class="tabular-nums text-muted-foreground">{{ rtrim(rtrim(number_format($row['percent'], 1), '0'), '.') }}%</x-ui.table.cell>
<x-ui.table.cell align="end" class="font-medium tabular-nums text-foreground">{{ $row['amount'] }}</x-ui.table.cell>
</x-ui.table.row>
@endforeach
</x-ui.table.body>
</x-ui.table>
</div>
</x-ui.tray>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.1 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0