Skip to content
Brok UI

Loading…

No results

Header

Open source

Site headers — a navbar (bar or floating pill shape), dropdown nav, a mega-menu and a product-site header with grouped panels.

Preview

8 of 8

resources/views/blocks/header-product.blade.php Blade
@props([
    'brand' => 'Acme',
    'brandHref' => '#',
    // Top-level destinations, in order. Two shapes:
    //   ['label' => 'Docs', 'href' => '#', 'active' => false]                 → plain link
    //   ['label' => 'Browse', 'columns' => 2, 'items' => [...entries]]        → panel
    // A panel entry is ['title', 'description', 'href', 'icon', 'meta', 'badge', 'active'];
    // `icon` is a key of $iconPaths below, `meta` a short trailing figure (a count),
    // `badge` a short label ("New"). A panel is active when any entry is.
    'nav' => null,
    'cta' => 'Get started',
    'ctaHref' => '#',
    // Optional end-side repository link; null hides it.
    'githubHref' => '#',
    'githubLabel' => 'GitHub',
    // The default search affordance is a static trigger; replace the `search`
    // slot with a real command palette or combobox.
    'searchLabel' => 'Search',
    'searchShortcut' => '⌘K',
    'navLabel' => 'Main',
    'menuLabel' => 'Menu',
    // id of the mobile <nav>, referenced by the hamburger's aria-controls.
    'menuId' => 'site-header-menu',
    // Inner row container; pass the app's own container classes so the brand
    // lines up with page content.
    'container' => 'mx-auto max-w-7xl px-4 sm:px-6 lg:px-8',
])

@php
    $nav ??= [
        ['label' => 'Docs', 'href' => '#', 'active' => true],
        [
            'label' => 'Browse',
            'columns' => 2,
            'items' => [
                ['title' => 'Components', 'description' => 'Accessible primitives you copy into your app.', 'href' => '#', 'icon' => 'grid', 'meta' => '180'],
                ['title' => 'Blocks', 'description' => 'Composed sections ready to drop into a page.', 'href' => '#', 'icon' => 'layers', 'meta' => '420'],
                ['title' => 'Pages', 'description' => 'Full screens assembled from blocks.', 'href' => '#', 'icon' => 'file', 'meta' => '60'],
                ['title' => 'Bundles', 'description' => 'Dependency sets that install together.', 'href' => '#', 'icon' => 'package', 'badge' => 'New'],
            ],
        ],
        [
            'label' => 'Tools',
            'items' => [
                ['title' => 'Builder', 'description' => 'Compose blocks into a page and export it.', 'href' => '#', 'icon' => 'layout'],
                ['title' => 'Themes', 'description' => 'Generate a token preset for your brand.', 'href' => '#', 'icon' => 'palette'],
            ],
        ],
        ['label' => 'Changelog', 'href' => '#'],
    ];

    $iconPaths = [
        'grid' => '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
        'layers' => '<rect x="3" y="4" width="18" height="6" rx="1.5"/><rect x="3" y="14" width="18" height="6" rx="1.5"/>',
        'file' => '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/>',
        'package' => '<path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
        'layout' => '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',
        'palette' => '<circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18c-1.5 0-2-.8-2-1.8 0-.9.4-1.2.4-2s-.6-1.2-1.7-1.2H7a4 4 0 0 1-4-4"/>',
        'book' => '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
        'sparkles' => '<path d="M12 3l1.9 4.6 4.6 1.9-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z"/><path d="M19 14l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8z"/>',
        'bolt' => '<path d="M13 2 3 14h7l-1 8 10-12h-7l1-8z"/>',
        'rocket' => '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/>',
        'shield' => '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
        'users' => '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
        'lifebuoy' => '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="m4.9 4.9 4.2 4.2M14.9 14.9l4.2 4.2M19.1 4.9l-4.2 4.2M9.1 14.9l-4.2 4.2"/>',
        'chart' => '<path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>',
        'code' => '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>',
    ];

    $isPanel = static fn (array $item): bool => ! empty($item['items']);
    $panelActive = static fn (array $item): bool => (bool) ($item['active'] ?? collect($item['items'] ?? [])->contains(fn ($entry) => ! empty($entry['active'])));
@endphp

<header {{ $attributes->merge(['class' => 'sticky top-0 z-sticky w-full border-b border-border bg-background/85 text-foreground backdrop-blur-md']) }}>
    <div class="{{ $container }} flex h-14 items-center gap-4 lg:gap-6">
        {{-- Brand, pinned to the start --}}
        @if (isset($logo) && $logo->isNotEmpty())
            {{ $logo }}
        @else
            <x-ui.brand :href="$brandHref" :label="$brand" size="sm" class="shrink-0" />
        @endif

        {{-- Primary nav: plain links and hover/focus panels --}}
        <div class="hidden min-w-0 lg:block">
        <x-ui.navigation-menu aria-label="{{ __($navLabel) }}">
            <x-ui.navigation-menu.list>
                @foreach ($nav as $item)
                    <x-ui.navigation-menu.item>
                        @if ($isPanel($item))
                            <x-ui.navigation-menu.trigger :active="$panelActive($item)">{{ __($item['label']) }}</x-ui.navigation-menu.trigger>
                            @php $wide = ($item['columns'] ?? 1) > 1; @endphp
                            <x-ui.navigation-menu.content :class="$wide ? 'w-[38rem] p-2' : 'w-80 p-2'">
                                <ul @class(['grid gap-2', 'sm:grid-cols-2' => $wide])>
                                    @foreach ($item['items'] as $entry)
                                        <li>
                                            <x-ui.navigation-menu.link :href="$entry['href'] ?? '#'" :active="! empty($entry['active'])" class="group">
                                                <span class="flex items-start gap-4">
                                                    <span class="grid size-9 shrink-0 place-items-center rounded-md border border-border bg-muted text-muted-foreground transition-colors group-hover:text-foreground motion-reduce:transition-none">
                                                        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{!! $iconPaths[$entry['icon'] ?? ''] ?? $iconPaths['bolt'] !!}</svg>
                                                    </span>
                                                    <span class="min-w-0 flex-1">
                                                        <span class="flex items-center gap-2">
                                                            <span class="truncate text-sm font-medium text-foreground">{{ __($entry['title']) }}</span>
                                                            @if (! empty($entry['badge']))
                                                                <x-ui.badge variant="secondary">{{ __($entry['badge']) }}</x-ui.badge>
                                                            @endif
                                                            @if (! empty($entry['meta']))
                                                                <span class="ms-auto shrink-0 text-xs tabular-nums text-muted-foreground">{{ $entry['meta'] }}</span>
                                                            @endif
                                                        </span>
                                                        @if (! empty($entry['description']))
                                                            <span class="mt-0.5 line-clamp-2 block text-xs text-muted-foreground">{{ __($entry['description']) }}</span>
                                                        @endif
                                                    </span>
                                                </span>
                                            </x-ui.navigation-menu.link>
                                        </li>
                                    @endforeach
                                </ul>
                            </x-ui.navigation-menu.content>
                        @else
                            <x-ui.navigation-menu.link :href="$item['href'] ?? '#'" :active="! empty($item['active'])" class="h-control-h-md px-control-x-md py-0 font-medium">
                                <span class="flex h-full items-center whitespace-nowrap">{{ __($item['label']) }}</span>
                            </x-ui.navigation-menu.link>
                        @endif
                    </x-ui.navigation-menu.item>
                @endforeach
            </x-ui.navigation-menu.list>
        </x-ui.navigation-menu>
        </div>

        {{-- Search + utility actions + CTA + mobile menu, pinned to the end --}}
        <div class="ms-auto flex min-w-0 flex-1 items-center justify-end gap-2 lg:flex-none">
            @if (isset($search) && $search->isNotEmpty())
                {{ $search }}
            @else
                <span class="hidden sm:block">
                    <x-ui.button variant="outline" size="sm" type="button" class="w-56 justify-start text-muted-foreground [&>[data-slot=button-label]]:flex-1 [&>[data-slot=button-label]]:text-start">
                        <x-slot:icon>
                            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
                        </x-slot:icon>
                        {{ __($searchLabel) }}
                        @if ($searchShortcut)
                            <x-slot:iconTrailing>
                                <x-ui.kbd>{{ $searchShortcut }}</x-ui.kbd>
                            </x-slot:iconTrailing>
                        @endif
                    </x-ui.button>
                </span>
            @endif

            @if ($githubHref)
                <span class="hidden sm:block">
                <x-ui.button :href="$githubHref" variant="outline" size="sm" :icon-label="__($githubLabel)" rel="noopener" class="text-muted-foreground">
                    <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.17c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.68-1.28-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.19 1.76 1.19 1.03 1.76 2.7 1.25 3.36.96.1-.75.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.17 1.18a11 11 0 0 1 5.77 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.8 1.19 1.83 1.19 3.09 0 4.42-2.7 5.39-5.26 5.68.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z"/></svg>
                </x-ui.button>
                </span>
            @endif

            {{ $actions ?? '' }}

            <span class="hidden lg:block">
                <x-ui.button :href="$ctaHref" size="sm" class="whitespace-nowrap">{{ __($cta) }}</x-ui.button>
            </span>

            {{-- Mobile menu: an end-side sheet --}}
            <x-ui.sheet>
                <x-ui.button
                    variant="outline"
                    size="sm"
                    :icon-label="__($menuLabel)"
                    class="text-muted-foreground lg:hidden"
                    x-on:click="show()"
                    aria-haspopup="dialog"
                    x-bind:aria-expanded="open"
                    aria-controls="{{ $menuId }}"
                    x-ref="trigger"
                >
                    <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
                </x-ui.button>

                <x-ui.sheet.content side="end" class="flex flex-col !p-0">
                    <div class="flex shrink-0 items-center justify-between gap-4 border-b border-border px-4 py-2">
                        <x-ui.sheet.title class="flex min-w-0 items-center text-sm">
                            @if (isset($logo) && $logo->isNotEmpty())
                                {{ $logo }}
                            @else
                                <x-ui.brand as="span" :label="$brand" size="sm" />
                            @endif
                        </x-ui.sheet.title>
                        <x-ui.button variant="ghost" size="sm" :icon-label="__('Close menu')" x-on:click="hide()" class="text-muted-foreground">
                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
                        </x-ui.button>
                    </div>

                    <nav id="{{ $menuId }}" aria-label="{{ __('Mobile') }}" class="flex-1 overflow-y-auto p-4">
                        <ul class="grid gap-2">
                            @foreach ($nav as $item)
                                @if ($isPanel($item))
                                    <li class="grid gap-2 pt-2">
                                        <p class="px-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ __($item['label']) }}</p>
                                        <ul class="grid gap-2">
                                            @foreach ($item['items'] as $entry)
                                                <li>
                                                    <a
                                                        href="{{ $entry['href'] ?? '#' }}"
                                                        data-active="{{ ! empty($entry['active']) ? 'true' : 'false' }}"
                                                        @if (! empty($entry['active'])) aria-current="page" @endif
                                                        class="flex h-control-h-md items-center gap-4 rounded-md px-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring data-[active=true]:bg-accent/50 motion-reduce:transition-none"
                                                    >
                                                        <span class="grid size-8 shrink-0 place-items-center rounded-md border border-border bg-muted text-muted-foreground">
                                                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{!! $iconPaths[$entry['icon'] ?? ''] ?? $iconPaths['bolt'] !!}</svg>
                                                        </span>
                                                        <span class="min-w-0 flex-1 truncate">{{ __($entry['title']) }}</span>
                                                        @if (! empty($entry['badge']))
                                                            <x-ui.badge variant="secondary">{{ __($entry['badge']) }}</x-ui.badge>
                                                        @endif
                                                        @if (! empty($entry['meta']))
                                                            <span class="shrink-0 text-xs tabular-nums text-muted-foreground">{{ $entry['meta'] }}</span>
                                                        @endif
                                                    </a>
                                                </li>
                                            @endforeach
                                        </ul>
                                    </li>
                                @else
                                    <li>
                                        <a
                                            href="{{ $item['href'] ?? '#' }}"
                                            data-active="{{ ! empty($item['active']) ? 'true' : 'false' }}"
                                            @if (! empty($item['active'])) aria-current="page" @endif
                                            class="flex h-control-h-md items-center rounded-md px-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring data-[active=true]:bg-accent/50 motion-reduce:transition-none"
                                        >{{ __($item['label']) }}</a>
                                    </li>
                                @endif
                            @endforeach
                        </ul>
                    </nav>

                    <div class="grid shrink-0 gap-2 border-t border-border p-4">
                        {{ $mobile ?? '' }}
                        @if ($githubHref)
                            <x-ui.button :href="$githubHref" variant="outline" rel="noopener" class="w-full sm:hidden">{{ __($githubLabel) }}</x-ui.button>
                        @endif
                        <x-ui.button :href="$ctaHref" class="w-full">{{ __($cta) }}</x-ui.button>
                    </div>
                </x-ui.sheet.content>
            </x-ui.sheet>
        </div>
    </div>
</header>

Installation

terminal
php artisan ui:add blocks/header-product

Installs these dependencies automatically:

Registry contract

Install confidence

php artisan ui:add blocks/header-product 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.2
License
open
Stability
stable
Contract
v4
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/blocks/header-product.blade.php

Registry dependencies

badge brand button kbd navigation-menu sheet

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.

header-product.md
# Brok UI block: Header Product (`header-product`)

A product-site header — brand, a navigation menu with plain links and hover/focus panels of icon-tiled entries (description, count, badge, active state), a search trigger slot, utility actions, a primary CTA, and an end-side sheet for the mobile menu.

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/header-product
```

## Render it

```blade
<x-blocks.header-product />
```

## Props

- `brand` (string, default `Acme`) — Brand name; its first letter fills the default mark.
- `brand-href` (string, default `#`) — Brand link target.
- `nav` (array|null, default `null`) — Top-level items. A link is ['label','href','active']; a panel is ['label','columns','items'] whose entries are ['title','description','href','icon','meta','badge','active']. A panel is active when any entry is.
- `cta` (string, default `Get started`) — Primary action label, shown on desktop and in the sheet footer.
- `cta-href` (string, default `#`) — Primary action target.
- `github-href` (string|null, default `#`) — Repository icon link (end side); null hides it.
- `github-label` (string, default `GitHub`) — Accessible name of the repository link.
- `search-label` (string, default `Search`) — Label of the default static search trigger.
- `search-shortcut` (string|null, default `⌘K`) — Shortcut hint rendered as a kbd in the default search trigger; null hides it.
- `nav-label` (string, default `Main`) — aria-label of the desktop navigation landmark.
- `menu-label` (string, default `Menu`) — Accessible name of the hamburger button.
- `menu-id` (string, default `site-header-menu`) — id of the mobile nav, referenced by the hamburger's aria-controls.
- `container` (string, default `mx-auto max-w-7xl px-4 sm:px-6 lg:px-8`) — Classes of the inner row container; pass the app's own container so the brand aligns with page content.

## Use when

- Use for a documentation or product site whose destinations group into a few panels (Browse, Tools) plus a couple of plain links.
- Use when the header must host app-owned controls (a search palette, a theme toggle) through slots instead of hard-coded markup.
- Use when the current destination must read as active in both the desktop menu and the mobile sheet.

## Avoid when

- Choose header-navbar for a flat list of four or fewer links with no panels.
- Choose header-mega-menu when the panel needs a featured promo card rather than a list of destinations.
- Choose app-header-* for an authenticated application shell with user menus and breadcrumbs.

## Rules

- Render the installed block with `<x-blocks.header-product />` 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/header-product
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/header-product.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

  • Use for a documentation or product site whose destinations group into a few panels (Browse, Tools) plus a couple of plain links.
  • Use when the header must host app-owned controls (a search palette, a theme toggle) through slots instead of hard-coded markup.
  • Use when the current destination must read as active in both the desktop menu and the mobile sheet.

Avoid when

  • Choose header-navbar for a flat list of four or fewer links with no panels.
  • Choose header-mega-menu when the panel needs a featured promo card rather than a list of destinations.
  • Choose app-header-* for an authenticated application shell with user menus and breadcrumbs.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
blocks/header-product navigation-menu navigation-menu.trigger navigation-menu.content navigation-menu.link button kbd badge sheet sheet.content sheet.title
Theming hooks
blocks/header-product navigation-menu sheet button

Usage

Render the block as a component, passing data where useful:

resources/views/welcome.blade.php Blade
<x-blocks.header-product />

Built from primitives

Composes <brok:*> Semantic tokens Re-themeable RTL-ready

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.

resources/views/blocks/header-product.blade.php Blade
@props([
    'brand' => 'Acme',
    'brandHref' => '#',
    // Top-level destinations, in order. Two shapes:
    //   ['label' => 'Docs', 'href' => '#', 'active' => false]                 → plain link
    //   ['label' => 'Browse', 'columns' => 2, 'items' => [...entries]]        → panel
    // A panel entry is ['title', 'description', 'href', 'icon', 'meta', 'badge', 'active'];
    // `icon` is a key of $iconPaths below, `meta` a short trailing figure (a count),
    // `badge` a short label ("New"). A panel is active when any entry is.
    'nav' => null,
    'cta' => 'Get started',
    'ctaHref' => '#',
    // Optional end-side repository link; null hides it.
    'githubHref' => '#',
    'githubLabel' => 'GitHub',
    // The default search affordance is a static trigger; replace the `search`
    // slot with a real command palette or combobox.
    'searchLabel' => 'Search',
    'searchShortcut' => '⌘K',
    'navLabel' => 'Main',
    'menuLabel' => 'Menu',
    // id of the mobile <nav>, referenced by the hamburger's aria-controls.
    'menuId' => 'site-header-menu',
    // Inner row container; pass the app's own container classes so the brand
    // lines up with page content.
    'container' => 'mx-auto max-w-7xl px-4 sm:px-6 lg:px-8',
])

@php
    $nav ??= [
        ['label' => 'Docs', 'href' => '#', 'active' => true],
        [
            'label' => 'Browse',
            'columns' => 2,
            'items' => [
                ['title' => 'Components', 'description' => 'Accessible primitives you copy into your app.', 'href' => '#', 'icon' => 'grid', 'meta' => '180'],
                ['title' => 'Blocks', 'description' => 'Composed sections ready to drop into a page.', 'href' => '#', 'icon' => 'layers', 'meta' => '420'],
                ['title' => 'Pages', 'description' => 'Full screens assembled from blocks.', 'href' => '#', 'icon' => 'file', 'meta' => '60'],
                ['title' => 'Bundles', 'description' => 'Dependency sets that install together.', 'href' => '#', 'icon' => 'package', 'badge' => 'New'],
            ],
        ],
        [
            'label' => 'Tools',
            'items' => [
                ['title' => 'Builder', 'description' => 'Compose blocks into a page and export it.', 'href' => '#', 'icon' => 'layout'],
                ['title' => 'Themes', 'description' => 'Generate a token preset for your brand.', 'href' => '#', 'icon' => 'palette'],
            ],
        ],
        ['label' => 'Changelog', 'href' => '#'],
    ];

    $iconPaths = [
        'grid' => '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
        'layers' => '<rect x="3" y="4" width="18" height="6" rx="1.5"/><rect x="3" y="14" width="18" height="6" rx="1.5"/>',
        'file' => '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/>',
        'package' => '<path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
        'layout' => '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',
        'palette' => '<circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18c-1.5 0-2-.8-2-1.8 0-.9.4-1.2.4-2s-.6-1.2-1.7-1.2H7a4 4 0 0 1-4-4"/>',
        'book' => '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
        'sparkles' => '<path d="M12 3l1.9 4.6 4.6 1.9-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z"/><path d="M19 14l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8z"/>',
        'bolt' => '<path d="M13 2 3 14h7l-1 8 10-12h-7l1-8z"/>',
        'rocket' => '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/>',
        'shield' => '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
        'users' => '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
        'lifebuoy' => '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="m4.9 4.9 4.2 4.2M14.9 14.9l4.2 4.2M19.1 4.9l-4.2 4.2M9.1 14.9l-4.2 4.2"/>',
        'chart' => '<path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>',
        'code' => '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>',
    ];

    $isPanel = static fn (array $item): bool => ! empty($item['items']);
    $panelActive = static fn (array $item): bool => (bool) ($item['active'] ?? collect($item['items'] ?? [])->contains(fn ($entry) => ! empty($entry['active'])));
@endphp

<header {{ $attributes->merge(['class' => 'sticky top-0 z-sticky w-full border-b border-border bg-background/85 text-foreground backdrop-blur-md']) }}>
    <div class="{{ $container }} flex h-14 items-center gap-4 lg:gap-6">
        {{-- Brand, pinned to the start --}}
        @if (isset($logo) && $logo->isNotEmpty())
            {{ $logo }}
        @else
            <x-ui.brand :href="$brandHref" :label="$brand" size="sm" class="shrink-0" />
        @endif

        {{-- Primary nav: plain links and hover/focus panels --}}
        <div class="hidden min-w-0 lg:block">
        <x-ui.navigation-menu aria-label="{{ __($navLabel) }}">
            <x-ui.navigation-menu.list>
                @foreach ($nav as $item)
                    <x-ui.navigation-menu.item>
                        @if ($isPanel($item))
                            <x-ui.navigation-menu.trigger :active="$panelActive($item)">{{ __($item['label']) }}</x-ui.navigation-menu.trigger>
                            @php $wide = ($item['columns'] ?? 1) > 1; @endphp
                            <x-ui.navigation-menu.content :class="$wide ? 'w-[38rem] p-2' : 'w-80 p-2'">
                                <ul @class(['grid gap-2', 'sm:grid-cols-2' => $wide])>
                                    @foreach ($item['items'] as $entry)
                                        <li>
                                            <x-ui.navigation-menu.link :href="$entry['href'] ?? '#'" :active="! empty($entry['active'])" class="group">
                                                <span class="flex items-start gap-4">
                                                    <span class="grid size-9 shrink-0 place-items-center rounded-md border border-border bg-muted text-muted-foreground transition-colors group-hover:text-foreground motion-reduce:transition-none">
                                                        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{!! $iconPaths[$entry['icon'] ?? ''] ?? $iconPaths['bolt'] !!}</svg>
                                                    </span>
                                                    <span class="min-w-0 flex-1">
                                                        <span class="flex items-center gap-2">
                                                            <span class="truncate text-sm font-medium text-foreground">{{ __($entry['title']) }}</span>
                                                            @if (! empty($entry['badge']))
                                                                <x-ui.badge variant="secondary">{{ __($entry['badge']) }}</x-ui.badge>
                                                            @endif
                                                            @if (! empty($entry['meta']))
                                                                <span class="ms-auto shrink-0 text-xs tabular-nums text-muted-foreground">{{ $entry['meta'] }}</span>
                                                            @endif
                                                        </span>
                                                        @if (! empty($entry['description']))
                                                            <span class="mt-0.5 line-clamp-2 block text-xs text-muted-foreground">{{ __($entry['description']) }}</span>
                                                        @endif
                                                    </span>
                                                </span>
                                            </x-ui.navigation-menu.link>
                                        </li>
                                    @endforeach
                                </ul>
                            </x-ui.navigation-menu.content>
                        @else
                            <x-ui.navigation-menu.link :href="$item['href'] ?? '#'" :active="! empty($item['active'])" class="h-control-h-md px-control-x-md py-0 font-medium">
                                <span class="flex h-full items-center whitespace-nowrap">{{ __($item['label']) }}</span>
                            </x-ui.navigation-menu.link>
                        @endif
                    </x-ui.navigation-menu.item>
                @endforeach
            </x-ui.navigation-menu.list>
        </x-ui.navigation-menu>
        </div>

        {{-- Search + utility actions + CTA + mobile menu, pinned to the end --}}
        <div class="ms-auto flex min-w-0 flex-1 items-center justify-end gap-2 lg:flex-none">
            @if (isset($search) && $search->isNotEmpty())
                {{ $search }}
            @else
                <span class="hidden sm:block">
                    <x-ui.button variant="outline" size="sm" type="button" class="w-56 justify-start text-muted-foreground [&>[data-slot=button-label]]:flex-1 [&>[data-slot=button-label]]:text-start">
                        <x-slot:icon>
                            <svg class="size-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
                        </x-slot:icon>
                        {{ __($searchLabel) }}
                        @if ($searchShortcut)
                            <x-slot:iconTrailing>
                                <x-ui.kbd>{{ $searchShortcut }}</x-ui.kbd>
                            </x-slot:iconTrailing>
                        @endif
                    </x-ui.button>
                </span>
            @endif

            @if ($githubHref)
                <span class="hidden sm:block">
                <x-ui.button :href="$githubHref" variant="outline" size="sm" :icon-label="__($githubLabel)" rel="noopener" class="text-muted-foreground">
                    <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2.17c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.68-1.28-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.19 1.76 1.19 1.03 1.76 2.7 1.25 3.36.96.1-.75.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.17 1.18a11 11 0 0 1 5.77 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.8 1.19 1.83 1.19 3.09 0 4.42-2.7 5.39-5.26 5.68.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z"/></svg>
                </x-ui.button>
                </span>
            @endif

            {{ $actions ?? '' }}

            <span class="hidden lg:block">
                <x-ui.button :href="$ctaHref" size="sm" class="whitespace-nowrap">{{ __($cta) }}</x-ui.button>
            </span>

            {{-- Mobile menu: an end-side sheet --}}
            <x-ui.sheet>
                <x-ui.button
                    variant="outline"
                    size="sm"
                    :icon-label="__($menuLabel)"
                    class="text-muted-foreground lg:hidden"
                    x-on:click="show()"
                    aria-haspopup="dialog"
                    x-bind:aria-expanded="open"
                    aria-controls="{{ $menuId }}"
                    x-ref="trigger"
                >
                    <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
                </x-ui.button>

                <x-ui.sheet.content side="end" class="flex flex-col !p-0">
                    <div class="flex shrink-0 items-center justify-between gap-4 border-b border-border px-4 py-2">
                        <x-ui.sheet.title class="flex min-w-0 items-center text-sm">
                            @if (isset($logo) && $logo->isNotEmpty())
                                {{ $logo }}
                            @else
                                <x-ui.brand as="span" :label="$brand" size="sm" />
                            @endif
                        </x-ui.sheet.title>
                        <x-ui.button variant="ghost" size="sm" :icon-label="__('Close menu')" x-on:click="hide()" class="text-muted-foreground">
                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
                        </x-ui.button>
                    </div>

                    <nav id="{{ $menuId }}" aria-label="{{ __('Mobile') }}" class="flex-1 overflow-y-auto p-4">
                        <ul class="grid gap-2">
                            @foreach ($nav as $item)
                                @if ($isPanel($item))
                                    <li class="grid gap-2 pt-2">
                                        <p class="px-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">{{ __($item['label']) }}</p>
                                        <ul class="grid gap-2">
                                            @foreach ($item['items'] as $entry)
                                                <li>
                                                    <a
                                                        href="{{ $entry['href'] ?? '#' }}"
                                                        data-active="{{ ! empty($entry['active']) ? 'true' : 'false' }}"
                                                        @if (! empty($entry['active'])) aria-current="page" @endif
                                                        class="flex h-control-h-md items-center gap-4 rounded-md px-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring data-[active=true]:bg-accent/50 motion-reduce:transition-none"
                                                    >
                                                        <span class="grid size-8 shrink-0 place-items-center rounded-md border border-border bg-muted text-muted-foreground">
                                                            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{!! $iconPaths[$entry['icon'] ?? ''] ?? $iconPaths['bolt'] !!}</svg>
                                                        </span>
                                                        <span class="min-w-0 flex-1 truncate">{{ __($entry['title']) }}</span>
                                                        @if (! empty($entry['badge']))
                                                            <x-ui.badge variant="secondary">{{ __($entry['badge']) }}</x-ui.badge>
                                                        @endif
                                                        @if (! empty($entry['meta']))
                                                            <span class="shrink-0 text-xs tabular-nums text-muted-foreground">{{ $entry['meta'] }}</span>
                                                        @endif
                                                    </a>
                                                </li>
                                            @endforeach
                                        </ul>
                                    </li>
                                @else
                                    <li>
                                        <a
                                            href="{{ $item['href'] ?? '#' }}"
                                            data-active="{{ ! empty($item['active']) ? 'true' : 'false' }}"
                                            @if (! empty($item['active'])) aria-current="page" @endif
                                            class="flex h-control-h-md items-center rounded-md px-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring data-[active=true]:bg-accent/50 motion-reduce:transition-none"
                                        >{{ __($item['label']) }}</a>
                                    </li>
                                @endif
                            @endforeach
                        </ul>
                    </nav>

                    <div class="grid shrink-0 gap-2 border-t border-border p-4">
                        {{ $mobile ?? '' }}
                        @if ($githubHref)
                            <x-ui.button :href="$githubHref" variant="outline" rel="noopener" class="w-full sm:hidden">{{ __($githubLabel) }}</x-ui.button>
                        @endif
                        <x-ui.button :href="$ctaHref" class="w-full">{{ __($cta) }}</x-ui.button>
                    </div>
                </x-ui.sheet.content>
            </x-ui.sheet>
        </div>
    </div>
</header>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.0.2
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v4
Foundation
≥ 1.0.0