Skip to content
Brok UI

Loading…

No results

Pane Heading

Open source

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").

Version
v1.1.0
Stability
stable
License
MIT
Related
Section Heading
Page Header
List Panel
Card

Preview

Revisions

12 revisions

Every saved version of this note, newest first.

Capture context

3
Variant
previews.components.pane-heading.default.blade.php 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>
Default Current
Label Current
Page Current

Installation

terminal
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.

  • blade resources/views/components/ui/pane-heading.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.

pane-heading.md
# 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

label.blade.php Blade
<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.blade.php Blade
<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>
page.blade.php Blade
<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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

pane-heading pane-heading-actions pane-heading-count pane-heading-description pane-heading-title

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

Layout and content structure

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
pane-heading pane-heading-title pane-heading-count pane-heading-description pane-heading-actions
Theming hooks
pane-heading

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
  • 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-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/pane-heading.blade.php Blade
@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