App shell
The page frame: ribbon, bar, body, footer. A four-row grid where the body takes the remaining height, so a short page still puts its footer at the bottom of the viewport.
Code
The three render the same class names, the same ARIA and the same data attributes. That is checked when this site builds: a class in any of these samples that neither the markup contract nor the shipped stylesheet defines fails the build.
<a class="wsu-skip-link" href="#main">Skip to main content</a>
<div class="wsu-shell wsu-shell--sidebar">
<header class="wsu-app-bar wsu-app-bar--compact">…</header>
<div class="wsu-shell__body">
<nav class="wsu-sidebar" id="app-sidebar" aria-label="Sections" hidden tabindex="-1">…</nav>
<main class="wsu-shell__main" id="main" tabindex="-1">
…
</main>
</div>
<footer class="wsu-footer">…</footer>
</div><x-wsu::app-shell title="Select courses">
{{-- The bar, sidebar, breadcrumbs, page header and footer are rendered
for you. Slots override any of them. --}}
<p>Page content.</p>
</x-wsu::app-shell><AppShell main-id="main">
<template #bar><AppBar title="Course Registration" /></template>
<template #sidebar>
<Sidebar label="Sections">
<SidebarNav :items="sections" />
</Sidebar>
</template>
<PageHeader title="Select courses" />
<p>Page content.</p>
<template #footer><Footer :links="footerLinks" /></template>
</AppShell>Why it works this way
- On the Blade path the shell owns the whole document, from <html> to </html>. The head is where the pre-paint theme script, the viewport meta and the stylesheet order live, and every one of those was something a 1.x consumer had to copy into their own layout and keep in step by hand.
- main carries tabindex="-1". Without it the browser scrolls to main when the skip link is used and leaves focus on body, so the next Tab goes back to the top of the page. The skip link then looks like it works for a mouse user and does nothing for a keyboard one.
- Nothing here depends on a magic offset. 1.x compensated for its fixed navbar with body { margin-top: 50px } and a sidebar at top: 51px, numbers that break the moment a nav item wraps or someone raises the text size.
Accessibility
- One h1 per page, rendered by the page header, never by the shell.
- The skip link is the first thing in the tab order (2.4.1).
- lang is set on html (3.1.1).
Success criteria in brackets refer to WCAG 2.2. Every one of them is checked by axe-core in both themes at three viewport widths before this page can be published. See Accessibility for what the gates are.
Props
Blade
| Attribute | Default | What it does |
|---|---|---|
title | null | Sets the document title. Falls back to the breadcrumb trail, then the application name. |
description | null | Sets meta name="description". Omitted from the head entirely when empty. |
lang | 'en' | Sets lang on html (3.1.1). |
template | config('wayne-ui.template') | "internal" or "public". Selects the chrome, the stylesheet and the favicon together. |
layout | inferred | "sidebar", "plain" or "centered". Inferred from whether a sidebar exists. |
brand | null | Passed to AppBar. Falls back to the application's own name. |
home | null | Passed to AppBar. Falls back to config('wayne-ui.home'). |
nav | null | Passed to AppBar and repeated in the sidebar drawer. Falls back to config('wayne-ui.nav'). |
sidebarItems | null | Passed to Sidebar. Falls back to config('wayne-ui.sidebar.items'). |
sidebarLabel | null | Passed to Sidebar. Falls back to config('wayne-ui.sidebar.label'). |
sidebarId | 'app-sidebar' | Shared between the bar's drawer toggle and the sidebar itself. |
search | null | Passed to AppBar. Falls back to config('wayne-ui.search.enabled'). |
user | null | Passed to AppBar. Falls back to the signed-in user, resolved from the auth guard. |
themeToggle | null | Passed to AppBar. Falls back to true. |
breadcrumbs | null | Toggles the automatic breadcrumb trail. Falls back to config('wayne-ui.breadcrumbs'). |
pageHeader | true | Set false to place your own h1 on a page that also pushes crumbs. |
heading | null | Overrides the page header's heading, via the breadcrumb service. |
subheading | null | Sets the page header's subheading, via the breadcrumb service. |
main | 'main' | id of the main element. Must match the skip link target. |
Vue
| Prop | Type and default | What it does |
|---|---|---|
sidebar | Boolean, inferred | Two-column layout. Inferred from the sidebar slot; set it explicitly when the slot is present but conditionally empty. |
centered | Boolean, false | Centered single-purpose page: sign-in, an error, a confirmation. |
panel | Boolean, false | Wraps the content in .wsu-shell__panel. Only meaningful when centered. |
mainId | String, 'main' | Must match the SkipLink target. |
In the markup contract
contract/markup-contract.json is extracted from the two static templates and every package tests against it. This is what it records for .wsu-shell.
| Template | Elements | Accessibility attributes | Uses |
|---|---|---|---|
| internal apps/examples/static-html/index.html | div | none recorded | 1 |
| public apps/examples/static-html/public-app.html | div | none recorded | 1 |