Skip to content

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.

Preview

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>

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

AttributeDefaultWhat it does
titlenullSets the document title. Falls back to the breadcrumb trail, then the application name.
descriptionnullSets meta name="description". Omitted from the head entirely when empty.
lang'en'Sets lang on html (3.1.1).
templateconfig('wayne-ui.template')"internal" or "public". Selects the chrome, the stylesheet and the favicon together.
layoutinferred"sidebar", "plain" or "centered". Inferred from whether a sidebar exists.
brandnullPassed to AppBar. Falls back to the application's own name.
homenullPassed to AppBar. Falls back to config('wayne-ui.home').
navnullPassed to AppBar and repeated in the sidebar drawer. Falls back to config('wayne-ui.nav').
sidebarItemsnullPassed to Sidebar. Falls back to config('wayne-ui.sidebar.items').
sidebarLabelnullPassed to Sidebar. Falls back to config('wayne-ui.sidebar.label').
sidebarId'app-sidebar'Shared between the bar's drawer toggle and the sidebar itself.
searchnullPassed to AppBar. Falls back to config('wayne-ui.search.enabled').
usernullPassed to AppBar. Falls back to the signed-in user, resolved from the auth guard.
themeTogglenullPassed to AppBar. Falls back to true.
breadcrumbsnullToggles the automatic breadcrumb trail. Falls back to config('wayne-ui.breadcrumbs').
pageHeadertrueSet false to place your own h1 on a page that also pushes crumbs.
headingnullOverrides the page header's heading, via the breadcrumb service.
subheadingnullSets the page header's subheading, via the breadcrumb service.
main'main'id of the main element. Must match the skip link target.

Vue

PropType and defaultWhat it does
sidebarBoolean, inferredTwo-column layout. Inferred from the sidebar slot; set it explicitly when the slot is present but conditionally empty.
centeredBoolean, falseCentered single-purpose page: sign-in, an error, a confirmation.
panelBoolean, falseWraps the content in .wsu-shell__panel. Only meaningful when centered.
mainIdString, '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.

TemplateElementsAccessibility attributesUses
internal apps/examples/static-html/index.htmldivnone recorded1
public apps/examples/static-html/public-app.htmldivnone recorded1