{{-- The scaffold: M3's structure of bars, rails and panes around the page, and the FAB, with navigation that changes shape with the window. M3 and Compose call it the scaffold (docs/reference/m3/foundations.md § Layout → Scaffold); before 2.0.0 it was ``. SealShare …the page's app bar… …the page… The navigation is M3's per breakpoint (docs/reference/m3/foundations.md § Layout and foundations-supplement.md § Breakpoints), and only those four numbers: - **Compact**, below `medium` (600px): a navigation bar with the destinations marked `bar`, pinned to the bottom. Everything else is in the modal rail, which slides in when something calls `$store.rail.show()` — put a menu button in the app bar for it, hidden from `medium`: ``. - **Medium** (600–839): the collapsed rail in the layout, 96px, and no bar; its menu button opens it expanded over a scrim, since 256px beside the page would leave the page too little. - **Expanded** (840–1199): a standard rail — in the layout, nothing covered — collapsed until the menu button expands it in place. - **Large and extra-large** (from 1200): the same standard rail, expanded to begin with (`rail.default`), which is what M3 prefers once there is room. From `expanded` the choice the menu button makes is remembered and applied before the first paint (`$store.rail`, ), so the rail never paints one width and snaps to the other. A visitor who has chosen keeps that choice in both bands; one who never has gets the breakpoint's own default. `--material-margin` is M3's window margin — 16px on a compact window, 24px from `medium` — and the content region is padded with it, so a page inside the scaffold writes no gutters of its own. Something meant to reach the window's edges opts out with `margin-inline: calc(var(--material-margin) * -1)`. Panes are ``, and two side by side are M3's canonical layouts from `expanded`, `` and ``; inside the content region they draw no margin of their own (resources/css/layout/scaffold.css sets `--md-layout-margin` there). `destinations` is a list of arrays: `title`, `icon` (a Material Symbol), `url`, and optionally `active` (by default: the URL is the page's; during a Livewire update request, the page the component was rendered on rather than the update endpoint), `badge` (`true` for a dot, or a count), `badgeLabel` (what a screen reader hears for the badge instead: "3 unread"), `section` (a heading the destination is grouped under in the rail; only an expanded rail shows it), `bar` (`false` keeps it out of the bottom bar; M3 wants three to five there) and `navigate` (`false` for a full page load instead of `wire:navigate`). Slots, each rendered once: `banner` (a bar across the whole window, above the rail and the page — M3's scaffold is bars, then rails, then panes), `brand` (beside the rail's menu button while it is expanded), `rail-header` (under it: one ``, which the rail morphs between a FAB and an extended FAB as it opens), `rail-footer` (at the foot of the rail: footer destinations, an account), `actions` (a row of icon buttons at the very foot, stacked in a column once the rail collapses to its narrow width — the same selectors `navigation-rail.css` matches "collapsed" with, since `resources/css/layout/scaffold.css` keys off the rail's own hooks rather than a breakpoint of its own: a theme toggle, sign out), `top` (the page's own bar, above the page and *beside* the rail), `fab` (the page's floating action button, see below) and the page itself. The rail is one element at every width, so what is in it is also in the modal rail a phone opens. `label` names both navigation landmarks ("Main"); `rail-width` is the expanded rail's width; `tall-bar` picks M3's 80px navigation bar over the 64px one, and the bottom offset every pinned thing reads follows it; `hide-bar-on-scroll` lets the bar leave the window while the page scrolls down, and `--material-bottom-bar` goes down and comes back with it; `hide-rail-when-collapsed` is M3's immersive configuration — from `expanded` the rail leaves the layout when the menu button collapses it, rather than narrowing to 96px, so the page has the whole window. The only way back is `$store.rail.show()`, so put a menu button in the app bar at every width, not just below `medium`. `banner` or `top` is a decision about what the bar belongs to: an application-wide bar — one search, one account menu, the same on every page — spans the window and the rail starts under it; a bar that titles the page belongs to the page, beside the rail. Put an app bar in one or the other, never both. A banner that pins itself to the top of the window says how tall it is — `style="--material-banner: 4rem"` on `` — so the rail sticks under it instead of behind it. The page is `
` with `wire:transition.navigate`, behind a skip link that is the first thing a keyboard reaches. The snackbar host (``) is part of the scaffold; on a compact window it, and a `fab` button, sit above the bottom bar through `--material-bottom-bar`: the bar's 64px, the bottom safe area (`--material-safe-bottom`, else the device's inset) and `--material-bottom-extra` (0px unless the application docks something, an offline banner, on top of the bar). `fab` places an `` as Compose's Scaffold places its floating action button: fixed at the bottom-end corner, 16px from the window's edges on a compact window and 24px from `medium` (M3's margins, foundations-supplement.md § Breakpoints; the FAB's own adaptive placement is the lower trailing corner, components-actions-communication-containment.md § FAB), above the navigation bar and the bottom safe area, and lifted over a snackbar while one shows — M3: a snackbar appears above a FAB, never in front of or behind one. Compose raises the snackbar over the FAB instead; here the snackbar host is one fixed element every page shares, so the FAB is what moves (resources/css/layout/scaffold.css). Use it or `rail-header`'s FAB, not both: M3 says not to show more than one FAB on a screen. It comes after the page's bar and before the page in focus order, where M3 puts a FAB (§ FAB → Accessibility: "the FAB should be prioritized in the page's focus order"). `overflow-x: clip` on the content region, below `expanded` only, is the backstop under every page: `overflow-x: hidden` would force `overflow-y` to `auto`, turn the region into a scroll container and break every `position: sticky` inside it (an app bar, a list-detail pane). A wide window never clips what overhangs on purpose. Nothing application-specific belongs in here: an app's destinations and chrome come in through the props and slots. The caller's `class` and `style` land on the root, `data-md-scaffold` itself — the element `style="--material-banner: 4rem"` above sets a custom property on. --}} @props([ 'destinations' => [], 'label' => null, 'railWidth' => '16rem', 'tallBar' => false, 'hideBarOnScroll' => false, 'hideRailWhenCollapsed' => false, ]) @php $label ??= __('Main'); $current = \Livewire\Livewire::isLivewireRequest() ? \Livewire\Livewire::originalUrl() : request()->url(); $items = collect($destinations) ->filter(fn ($item): bool => is_array($item) && filled($item['title'] ?? null)) ->map(fn (array $item): array => [ 'title' => (string) $item['title'], 'icon' => $item['icon'] ?? null, 'url' => $item['url'] ?? null, 'active' => (bool) ($item['active'] ?? (filled($item['url'] ?? null) && rtrim(url($item['url']), '/') === rtrim($current, '/'))), 'badge' => $item['badge'] ?? null, 'badgeLabel' => filled($item['badgeLabel'] ?? null) ? (string) $item['badgeLabel'] : null, 'section' => filled($item['section'] ?? null) ? (string) $item['section'] : null, 'bar' => ($item['bar'] ?? true) !== false, 'navigate' => ($item['navigate'] ?? true) !== false, ]) ->values(); $barItems = $items->where('bar', true)->values(); // Consecutive destinations under the same heading form one group, in the order given. $groups = $items->chunkWhile(fn (array $item, int $key, $chunk): bool => $item['section'] === $chunk->last()['section']); @endphp
only(['class', 'style']) }}> {{ __('Skip to content') }} @isset($banner)
{{ $banner }}
@endisset
@isset($brand) {{ $brand }} @endisset @isset($railHeader) {{ $railHeader }} @endisset @foreach ($groups as $group) @if ($group->first()['section'] !== null) @foreach ($group as $item) @endforeach @else @foreach ($group as $item) @endforeach @endif @endforeach @if (isset($railFooter) || isset($actions)) {{ $railFooter ?? '' }} @isset($actions)
{{ $actions }}
@endisset
@endif
{{ $top ?? '' }} @isset($fab)
{{ $fab }}
@endisset
{{ $slot }}
@if ($barItems->isNotEmpty())
@foreach ($barItems as $item) @endforeach
@endif