Diagrams
Dependency-free, token-themed relationship diagrams — flow, network, org chart, indented tree, mind map and fishbone.
Preview
A dependency-free SVG mind map. Pass `:root` for the central topic and `:branches` — each `["label" => string, "children" => [string, …]]` — to map your own balanced tree. Each branch gets a cycling semantic colour shared by its children.
<div class="space-y-4">
<x-ui.mind-map :height="380" label="{{ __('Launch plan') }}" />
<p class="text-sm text-muted-foreground">
{{ __('A dependency-free SVG mind map. Pass `:root` for the central topic and `:branches` — each `["label" => string, "children" => [string, …]]` — to map your own balanced tree. Each branch gets a cycling semantic colour shared by its children.') }}
</p>
</div>
Installation
php artisan ui:add mind-map
Registry contract
php artisan ui:add mind-map
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.
-
resources/views/components/ui/mind-map.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.
# Brok UI: Mind Map (`mind-map`)
Dependency-free static SVG mind map: a central topic with balanced left/right branches and stacked child nodes, connected by smooth token-coloured bezier links.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add mind-map
```
## Usage
```blade
<div class="space-y-4">
<x-ui.mind-map :height="380" label="{{ __('Launch plan') }}" />
<p class="text-sm text-muted-foreground">
{{ __('A dependency-free SVG mind map. Pass `:root` for the central topic and `:branches` — each `["label" => string, "children" => [string, …]]` — to map your own balanced tree. Each branch gets a cycling semantic colour shared by its children.') }}
</p>
</div>
```
## Props
- `root` (mixed|null, default `null`) — The central topic string. Defaults to a demo "Launch plan" topic.
- `branches` (mixed|null, default `null`) — First-level branches: each an object with label and a list of child label strings.
- `height` (int, default `380`) — SVG height in pixels; width fills the container.
- `label` (string, default `Mind map`) — Accessible name for the figure.
## Use when
- Use when hierarchy, causality, network structure, or process flow is the information itself.
- Brainstorming or presenting a central topic radiating out into free-form branches and sub-ideas.
- Needing a balanced left/right layout with no fixed direction or sequence between branches.
## Avoid when
- Do not use specialized diagrams when a list, table, or short paragraph would be easier to interpret.
- The structure is a strict reporting hierarchy; use org-chart instead.
- The steps follow a defined sequence or decision path; use flow-diagram instead.
## Anti-patterns
- Using a specialized diagram when linear content is clearer
## Rules
- Use the `<brok:mind-map>` tag (or `<x-ui.mind-map>`) 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/mind-map
- Registry JSON (files, props, contract): https://brokui.dev/r/open/mind-map.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
Launch Plan
{{-- The default map framed by chart-card with a track-count badge. --}}
<x-ui.chart-card heading="Launch plan" subheading="Workstreams at a glance" trend="5 tracks" trend-variant="neutral">
<x-ui.mind-map :height="380" label="{{ __('Launch plan') }}" />
</x-ui.chart-card>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| root | mixed | null | null | The central topic string. Defaults to a demo "Launch plan" topic. |
| branches | mixed | null | null | First-level branches: each an object with label and a list of child label strings. |
| height | int | 380 | SVG height in pixels; width fills the container. |
| label | string | Mind map | Accessible name for the figure. |
Slots
default— Unused; the map renders from the root and branches props only.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Renders without arguments using built-in demo data, so it previews immediately.
- Balances branches across left and right of the central topic and stacks each branch's children, connected by smooth token-coloured bezier links.
- Dependency-free SVG: no chart library, computed and laid out entirely in Blade.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Explain hierarchy, causality, network structure, or flow.
Use when
- Use when hierarchy, causality, network structure, or process flow is the information itself.
- Brainstorming or presenting a central topic radiating out into free-form branches and sub-ideas.
- Needing a balanced left/right layout with no fixed direction or sequence between branches.
Avoid when
- Do not use specialized diagrams when a list, table, or short paragraph would be easier to interpret.
- The structure is a strict reporting hierarchy; use org-chart instead.
- The steps follow a defined sequence or decision path; use flow-diagram instead.
Use instead
- List, table, or prose explanation
Anti-patterns
- Using a specialized diagram when linear content is clearer
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The figure carries role="img" with an aria-label from label, so a screen reader hears a named summary instead of parsing raw SVG.
- Every branch and node label renders as visible SVG text, never as a hover-only tooltip.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, 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 underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
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.
@props([
// Central topic (string).
'root' => null,
// Branches: each ['label' => string, 'children' => [string, ...]].
'branches' => null,
'height' => 380,
'label' => 'Mind map',
])
@php
// ── Demo data so it renders without args ───────────────────────────────
$root ??= __('Launch plan');
$branches ??= [
['label' => __('Marketing'), 'children' => [__('Campaigns'), __('Social'), __('Email')]],
['label' => __('Engineering'), 'children' => [__('API'), __('Frontend'), __('QA')]],
['label' => __('Sales'), 'children' => [__('Outreach'), __('Demos')]],
['label' => __('Design'), 'children' => [__('Brand'), __('UI kit'), __('Motion')]],
['label' => __('Support'), 'children' => [__('Docs'), __('Helpdesk')]],
];
$branches = array_values($branches);
// ── Geometry ────────────────────────────────────────────────────────────
$W = 760;
$H = max(200, (int) $height);
$cx = $W / 2;
$cy = $H / 2;
// Node sizing. Width grows with label length but is clamped, and the SVG
// text is centred so it never overflows the rounded rect.
$rootW = 150;
$rootH = 44;
$branchH = 32;
$leafH = 26;
$charW = 7.2; // approx advance per character at the chosen font size
$padX = 26; // horizontal padding inside a node
$branchMinW = 96;
$branchMaxW = 168;
$leafMinW = 78;
$leafMaxW = 152;
$sizeFor = fn (string $text, int $min, int $max) => (int) round(
max($min, min($max, strlen($text) * $charW + $padX))
);
// Column x-positions, measured from the root pill edges.
$branchGapX = 96; // gap between root edge and branch node
$leafGapX = 56; // gap between branch node and leaf node
// Split branches into right / left halves (balanced radial).
$half = (int) ceil(count($branches) / 2);
$rightIdx = array_slice(array_keys($branches), 0, $half);
$leftIdx = array_slice(array_keys($branches), $half);
// Cycle the categorical chart tokens per branch; children inherit the branch
// hue. These are the tokens for arbitrary, meaningless categories — the
// status tokens (info/success/warning/destructive) would tell a reader a
// branch is healthy or broken, and the sixth entry used to be a near-white
// surface token that painted one branch invisible.
$palette = ['text-chart-1', 'text-chart-2', 'text-chart-3', 'text-chart-4', 'text-chart-5'];
// Lay out one side. $dir = +1 (right) or -1 (left). Returns node records and
// connector paths. Branches are stacked vertically and centred on $cy; each
// branch's children are stacked centred on the branch node.
$layoutSide = function (array $idxs, int $dir) use (
$branches, $palette, $cx, $cy, $rootW, $branchH, $leafH,
$branchGapX, $leafGapX, $sizeFor, $branchMinW, $branchMaxW, $leafMinW, $leafMaxW, $H
) {
$nodes = [];
$leaves = [];
$links = [];
if (! $idxs) {
return [$nodes, $leaves, $links];
}
$branchVGap = 18;
$leafVGap = 10;
// Estimate each branch's vertical footprint (its children block).
$blockH = [];
foreach ($idxs as $i) {
$kids = array_values($branches[$i]['children'] ?? []);
$n = max(1, count($kids));
$blockH[$i] = $n * $leafH + ($n - 1) * $leafVGap;
}
$totalH = array_sum($blockH) + (count($idxs) - 1) * $branchVGap;
$startY = $cy - $totalH / 2;
$rootEdgeX = $cx + $dir * ($rootW / 2);
$branchCx = $cx + $dir * ($rootW / 2 + $branchGapX);
$cursor = $startY;
foreach ($idxs as $order => $i) {
$branch = $branches[$i];
$color = $palette[$i % count($palette)];
$kids = array_values($branch['children'] ?? []);
$by = $cursor + $blockH[$i] / 2; // branch vertical centre
$bw = $sizeFor($branch['label'], $branchMinW, $branchMaxW);
$branchEdgeX = $branchCx + $dir * ($bw / 2);
$nodes[] = [
'label' => $branch['label'],
'x' => $branchCx,
'y' => $by,
'w' => $bw,
'color' => $color,
];
// Connector: root edge → branch node (smooth horizontal bezier).
$bIn = $branchCx - $dir * ($bw / 2);
$mx = ($rootEdgeX + $bIn) / 2;
$links[] = [
'color' => $color,
'd' => sprintf(
'M%.2f %.2f C%.2f %.2f %.2f %.2f %.2f %.2f',
$rootEdgeX, $cy, $mx, $cy, $mx, $by, $bIn, $by,
),
];
// Leaves stacked, centred on the branch node.
$leafCx = $branchCx + $dir * ($bw / 2 + $leafGapX);
$ly = $by - $blockH[$i] / 2 + $leafH / 2;
foreach ($kids as $kid) {
$lw = $sizeFor($kid, $leafMinW, $leafMaxW);
$leaves[] = [
'label' => $kid,
'x' => $leafCx,
'y' => $ly,
'w' => $lw,
'color' => $color,
];
$lIn = $leafCx - $dir * ($lw / 2);
$lmx = ($branchEdgeX + $lIn) / 2;
$links[] = [
'color' => $color,
'd' => sprintf(
'M%.2f %.2f C%.2f %.2f %.2f %.2f %.2f %.2f',
$branchEdgeX, $by, $lmx, $by, $lmx, $ly, $lIn, $ly,
),
];
$ly += $leafH + $leafVGap;
}
$cursor += $blockH[$i] + $branchVGap;
}
return [$nodes, $leaves, $links];
};
[$rNodes, $rLeaves, $rLinks] = $layoutSide($rightIdx, 1);
[$lNodes, $lLeaves, $lLinks] = $layoutSide($leftIdx, -1);
$branchNodes = array_merge($rNodes, $lNodes);
$leafNodes = array_merge($rLeaves, $lLeaves);
$connectors = array_merge($rLinks, $lLinks);
@endphp
<div
data-slot="mind-map"
role="img"
aria-label="{{ $label }}"
{{ $attributes->merge(['class' => 'w-full text-foreground']) }}
>
<svg
viewBox="0 0 {{ $W }} {{ $H }}"
width="100%"
height="{{ $H }}"
preserveAspectRatio="xMidYMid meet"
class="h-auto w-full"
fill="none"
aria-hidden="true"
>
{{-- Connectors (behind nodes). --}}
@foreach ($connectors as $link)
<path
class="{{ $link['color'] }} opacity-40"
d="{{ $link['d'] }}"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
fill="none"
/>
@endforeach
{{-- Leaf nodes. --}}
@foreach ($leafNodes as $leaf)
<g data-slot="mind-map-node" class="{{ $leaf['color'] }}">
<rect
x="{{ round($leaf['x'] - $leaf['w'] / 2, 2) }}"
y="{{ round($leaf['y'] - $leafH / 2, 2) }}"
width="{{ $leaf['w'] }}"
height="{{ $leafH }}"
rx="{{ $leafH / 2 }}"
class="fill-card stroke-border"
stroke-width="1"
/>
<text
x="{{ round($leaf['x'], 2) }}"
y="{{ round($leaf['y'], 2) }}"
text-anchor="middle"
dominant-baseline="central"
class="pointer-events-none fill-foreground text-2xs"
>{{ $leaf['label'] }}</text>
</g>
@endforeach
{{-- Branch nodes. --}}
@foreach ($branchNodes as $node)
<g data-slot="mind-map-branch" class="{{ $node['color'] }}">
<rect
x="{{ round($node['x'] - $node['w'] / 2, 2) }}"
y="{{ round($node['y'] - $branchH / 2, 2) }}"
width="{{ $node['w'] }}"
height="{{ $branchH }}"
rx="{{ $branchH / 2 }}"
class="fill-card stroke-current"
stroke-width="1.5"
/>
<text
x="{{ round($node['x'], 2) }}"
y="{{ round($node['y'], 2) }}"
text-anchor="middle"
dominant-baseline="central"
class="pointer-events-none fill-foreground text-xs font-medium"
>{{ $node['label'] }}</text>
</g>
@endforeach
{{-- Central root pill. --}}
<g data-slot="mind-map-root">
<rect
x="{{ round($cx - $rootW / 2, 2) }}"
y="{{ round($cy - $rootH / 2, 2) }}"
width="{{ $rootW }}"
height="{{ $rootH }}"
rx="{{ $rootH / 2 }}"
class="fill-primary"
/>
<text
x="{{ round($cx, 2) }}"
y="{{ round($cy, 2) }}"
text-anchor="middle"
dominant-baseline="central"
class="pointer-events-none fill-primary-foreground text-xs font-semibold"
>{{ $root }}</text>
</g>
</svg>
</div>
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