Pane Heading
A dense heading for a pane or section inside an application screen: an h2 to h4 title with an optional count, description and actions slot, or the h1 title of an application page (variant="page").
Preview
Revisions
Every saved version of this note, newest first.
Capture context
<section aria-labelledby="pane-heading-revisions" class="flex w-full max-w-xl flex-col gap-4 rounded-lg border border-border bg-card p-4">
<x-ui.pane-heading
:title="__('Revisions')"
heading-id="pane-heading-revisions"
:count="12"
:count-label="__('12 revisions')"
:description="__('Every saved version of this note, newest first.')"
>
<x-slot:actions>
<x-ui.button variant="outline" size="sm">{{ __('Compare') }}</x-ui.button>
<x-ui.button size="sm">{{ __('Restore') }}</x-ui.button>
</x-slot:actions>
</x-ui.pane-heading>
<x-ui.pane-heading as="h3" variant="label" :title="__('Capture context')" :count="3" />
</section>
Installation
php artisan ui:add pane-heading
Registry contract
php artisan ui:add pane-heading
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/pane-heading.blade.php
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: Pane Heading (`pane-heading`)
A dense heading for a pane or section inside an application screen: an h2 to h4 title with an optional count, description and actions slot, or the h1 title of an application page (variant="page").
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add pane-heading
```
## Usage
```blade
<section aria-labelledby="pane-heading-revisions" class="flex w-full max-w-xl flex-col gap-4 rounded-lg border border-border bg-card p-4">
<x-ui.pane-heading
:title="__('Revisions')"
heading-id="pane-heading-revisions"
:count="12"
:count-label="__('12 revisions')"
:description="__('Every saved version of this note, newest first.')"
>
<x-slot:actions>
<x-ui.button variant="outline" size="sm">{{ __('Compare') }}</x-ui.button>
<x-ui.button size="sm">{{ __('Restore') }}</x-ui.button>
</x-slot:actions>
</x-ui.pane-heading>
<x-ui.pane-heading as="h3" variant="label" :title="__('Capture context')" :count="3" />
</section>
```
## Props
- `title` (string|null, default `null`) — The heading text. The default slot wins when it has content; a slot that holds only HTML comments or Livewire block markers (an @if that rendered nothing) counts as empty. With no title and an empty slot nothing renders.
- `as` ('h1'|'h2'|'h3'|'h4'|null, default `null`) — Heading level, to fit the outline of the screen. h2 when not set; the page variant defaults to h1 and is the only variant that accepts h1. Other values fall back to the default level.
- `variant` (default|label|page, default `default`) — default: a foreground title for a pane. label: a small muted title for a field group or sub-list. page: the larger h1 title at the top of an application page.
- `description` (string|null, default `null`) — A supporting line under the title.
- `count` (int|string|null, default `null`) — A count badge beside the title.
- `countLabel` (string|null, default `null`) — Screen-reader text for the count ("12 revisions"); the visible number is then hidden from assistive tech.
- `headingId` (string|null, default `null`) — Id on the heading element, so a region can use aria-labelledby.
## Use when
- Use to structure hierarchy, spacing, and responsiveness so content is easier to scan and navigate.
- Titling a pane, panel or section inside an application screen (a list, a detail pane, a settings group) with an optional count and pane-level actions.
- A group of fields or a sub-list needs a small muted label title at the right heading level (variant="label").
- The h1 title at the top of an application page, with its count, description and page actions (variant="page").
## Avoid when
- Do not let layout primitives substitute for semantics, headings, or interaction rules users still need.
- A marketing section lede with an overline and large type; use section-heading.
- The first line of an admin page with a scope sentence; use admin-page-header.
## Anti-patterns
- Using visual layout as a substitute for semantic structure
## Rules
- Use the `<brok:pane-heading>` tag (or `<x-ui.pane-heading>`) 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/pane-heading
- Registry JSON (files, props, contract): https://brokui.dev/r/open/pane-heading.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
<div class="flex w-full max-w-md flex-col gap-4">
<x-ui.pane-heading as="h3" variant="label" :title="__('Resources')" :count="__('4/10')" :count-label="__('4 of 10 resources')" />
<x-ui.pane-heading as="h3" variant="label" :title="__('Agent settings')" :description="__('Model, tools and limits for this agent.')" />
<x-ui.pane-heading as="h4" variant="label" :title="__('Sources')">
<x-slot:actions>
<x-ui.button variant="ghost" size="sm">{{ __('Add source') }}</x-ui.button>
</x-slot:actions>
</x-ui.pane-heading>
</div>
Long Content
<div class="w-full max-w-xs">
<x-ui.pane-heading
:title="__('A deliberately long pane title that verifies wrapping, overflow and content expansion without clipping')"
:count="1284"
:description="__('A long supporting line that wraps onto several lines in a narrow pane instead of pushing the actions out of view.')"
>
<x-slot:actions>
<x-ui.button variant="outline" size="sm">{{ __('Export everything') }}</x-ui.button>
</x-slot:actions>
</x-ui.pane-heading>
</div>
<div class="flex w-full max-w-2xl flex-col gap-6">
<x-ui.pane-heading variant="page" heading-id="inbox-title" :title="__('Inbox')" :count="12" :count-label="__('12 pending items')" :description="__('New captures wait here until you accept or dismiss them.')">
<x-slot:actions>
<x-ui.button variant="outline" size="sm">{{ __('Process all') }}</x-ui.button>
</x-slot:actions>
</x-ui.pane-heading>
<x-ui.pane-heading :title="__('Today')" :count="3" />
</div>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | null | null | The heading text. The default slot wins when it has content; a slot that holds only HTML comments or Livewire block markers (an @if that rendered nothing) counts as empty. With no title and an empty slot nothing renders. |
| as | 'h1' | 'h2' | 'h3' | 'h4' | null | null | Heading level, to fit the outline of the screen. h2 when not set; the page variant defaults to h1 and is the only variant that accepts h1. Other values fall back to the default level. |
| variant | default | label | page | default | default: a foreground title for a pane. label: a small muted title for a field group or sub-list. page: the larger h1 title at the top of an application page. |
| description | string | null | null | A supporting line under the title. |
| count | int | string | null | null | A count badge beside the title. |
| countLabel | string | null | null | Screen-reader text for the count ("12 revisions"); the visible number is then hidden from assistive tech. |
| headingId | string | null | null | Id on the heading element, so a region can use aria-labelledby. |
Slots
default— Heading content with inline markup; wins over title.actions— Pane-level actions (small buttons, a menu, a link). They sit at the end of the title row and wrap under it on a narrow pane.
Renders as h1, h2, h3, h4.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- The root is a div, not a header, so it never becomes a banner landmark; the pane owns its own region and padding.
- Long titles wrap instead of truncating, and the actions wrap below the title on a narrow pane.
- The slot check ignores HTML comments, so Livewire's <!--[if BLOCK]--> markers around an empty @if do not hide the title prop.
- Declares registry capability flags: a11y, authoredStateFixtures, responsive, rtl, darkMode, localized.
Guidance
Structure content hierarchy and responsive relationships.
Use when
- Use to structure hierarchy, spacing, and responsiveness so content is easier to scan and navigate.
- Titling a pane, panel or section inside an application screen (a list, a detail pane, a settings group) with an optional count and pane-level actions.
- A group of fields or a sub-list needs a small muted label title at the right heading level (variant="label").
- The h1 title at the top of an application page, with its count, description and page actions (variant="page").
Avoid when
- Do not let layout primitives substitute for semantics, headings, or interaction rules users still need.
- A marketing section lede with an overline and large type; use section-heading.
- The first line of an admin page with a scope sentence; use admin-page-header.
Use instead
- Semantic HTML with standard flow
Anti-patterns
- Using visual layout as a substitute for semantic structure
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- Pick the level with as so the screen keeps one heading outline; pass heading-id and point the pane's aria-labelledby at it.
- The count is text in a badge; with count-label a screen reader hears the full phrase instead of a bare number.
- Use variant="page" once per page for its h1; the panes below it start at h2.
- 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([
// The heading text. The default slot wins when it has content, so the
// title can carry inline markup (a code name, an icon).
'title' => null,
// Heading level for the outline of the screen: h2, h3 or h4 (h2 when
// not set). The page variant also accepts h1 and defaults to it.
'as' => null,
// default: a foreground title for a pane or panel. label: a small muted
// title for a group of fields or a sub-list inside a pane. page: the
// larger h1 title at the top of an application page.
'variant' => 'default',
// Optional supporting line under the title.
'description' => null,
// Optional count beside the title (a number or a short string).
'count' => null,
// Screen-reader text for the count, e.g. "12 revisions". The visible
// count is hidden from assistive tech when this is set.
'countLabel' => null,
// Id for the heading element, so a region can name itself with
// aria-labelledby.
'headingId' => null,
])
@php
// An application pane or section title — the dense counterpart of the
// marketing section-heading. It only lays out the title row (title,
// count), the optional description and the actions slot; the pane itself
// owns its padding, border and landmark. The root is a div, never a
// <header>, so it does not become a banner landmark outside a section.
// Accept a string, a backed enum or a Stringable for `variant`.
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$variant = $styles['normalizeVariant']($variant);
$variant = in_array($variant, ['default', 'label', 'page'], true) ? $variant : 'default';
// Only the page variant may be an h1, so an existing as="h1" on a pane
// keeps its h2 fallback.
$tag = in_array($as, ['h1', 'h2', 'h3', 'h4'], true) && ($as !== 'h1' || $variant === 'page')
? $as
: ($variant === 'page' ? 'h1' : 'h2');
// hasActualContent() ignores HTML comments, so a slot that holds only
// Livewire's block markers (an @if that rendered nothing) or a comment
// counts as empty and the title prop renders.
$hasSlot = $slot instanceof \Illuminate\View\ComponentSlot ? $slot->hasActualContent() : trim((string) $slot) !== '';
$hasTitle = $hasSlot || filled($title);
$hasCount = $count !== null && $count !== '';
$titleClasses = [
'default' => 'text-sm font-semibold text-foreground',
'label' => 'text-xs font-medium text-muted-foreground',
'page' => 'text-base font-semibold tracking-tight text-foreground',
];
$descriptionClasses = [
'default' => 'text-sm text-muted-foreground',
'label' => 'text-xs text-muted-foreground',
'page' => 'text-sm text-muted-foreground',
];
@endphp
@if ($hasTitle)
<div
data-slot="pane-heading"
data-variant="{{ $variant }}"
{{ $attributes->merge(['class' => 'flex min-w-0 flex-wrap items-center justify-between gap-x-4 gap-y-2']) }}
>
<div class="flex min-w-0 grow basis-48 flex-col gap-1">
<div class="flex min-w-0 items-center gap-2">
<{{ $tag }}
data-slot="pane-heading-title"
@if (filled($headingId)) id="{{ $headingId }}" @endif
class="min-w-0 text-pretty break-words {{ $titleClasses[$variant] }}"
>{{ $hasSlot ? $slot : $title }}</{{ $tag }}>
@if ($hasCount)
<x-ui.badge variant="secondary" size="sm" data-slot="pane-heading-count" class="shrink-0 tabular-nums">
@if (filled($countLabel))
<span aria-hidden="true">{{ $count }}</span>
<span class="sr-only">{{ $countLabel }}</span>
@else
{{ $count }}
@endif
</x-ui.badge>
@endif
</div>
@if (filled($description))
<p data-slot="pane-heading-description" class="min-w-0 text-pretty break-words {{ $descriptionClasses[$variant] }}">{{ $description }}</p>
@endif
</div>
@isset($actions)
<div data-slot="pane-heading-actions" class="flex min-w-0 shrink-0 flex-wrap items-center gap-2">
{{ $actions }}
</div>
@endisset
</div>
@endif
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.0 - 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