Sidebar
Section navigation. Docked at desktop widths, a drawer below them.
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.
<div class="wsu-drawer-backdrop" data-wsu-drawer-backdrop="app-sidebar" hidden></div>
<nav class="wsu-sidebar" id="app-sidebar" aria-label="Sections" hidden tabindex="-1">
<div class="wsu-sidebar__inner">
<button class="wsu-btn wsu-btn--icon wsu-btn--ghost wsu-sidebar__close" type="button" data-wsu-drawer-close>
<span class="wsu-sr-only">Close navigation menu</span>
<svg class="wsu-icon" aria-hidden="true"><use href="/assets/wayne-ui/icons.svg#wsu-x"/></svg>
</button>
<div class="wsu-sidebar__primary-nav">
<ul class="wsu-sidebar__list" aria-label="Primary">
<li><a class="wsu-sidebar__link" href="/term" aria-current="page">Term</a></li>
<li><a class="wsu-sidebar__link" href="/catalog">Catalog</a></li>
</ul>
</div>
<div>
<p class="wsu-sidebar__heading" id="nav-registration">Registration</p>
<ul class="wsu-sidebar__list" aria-labelledby="nav-registration">
<li><a class="wsu-sidebar__link" href="/courses" aria-current="page">
<svg class="wsu-icon" aria-hidden="true"><use href="/assets/wayne-ui/icons.svg#wsu-clipboard-list"/></svg>
Select courses</a></li>
<li><a class="wsu-sidebar__link" href="/schedule">
<svg class="wsu-icon" aria-hidden="true"><use href="/assets/wayne-ui/icons.svg#wsu-calendar"/></svg>
My schedule</a></li>
</ul>
</div>
</div>
</nav>{{-- nav defaults to config('wayne-ui.nav'), the same list AppBar reads,
so the drawer's copy of Term and Catalog stays in step with the bar's
own without repeating the array here. --}}
<x-wsu::sidebar label="Sections" :items="[
['heading' => 'Registration', 'items' => [
['label' => 'Select courses', 'url' => '/courses', 'route' => 'courses.*', 'icon' => 'clipboard-list'],
['label' => 'My schedule', 'url' => '/schedule', 'route' => 'schedule.*', 'icon' => 'calendar'],
]],
]" /><Sidebar label="Sections">
<template #primary-nav>
<li><a class="wsu-sidebar__link" href="/term" aria-current="page">Term</a></li>
<li><a class="wsu-sidebar__link" href="/catalog">Catalog</a></li>
</template>
<SidebarNav
heading="Registration"
:items="[
{ label: 'Select courses', href: '/courses', icon: 'clipboard-list', current: true },
{ label: 'My schedule', href: '/schedule', icon: 'calendar' },
]"
/>
</Sidebar>Why it works this way
- Label it for what it holds. "Sections" or "Filters", not "Navigation": a screen reader already announces the role, so aria-label="Navigation" produces "Navigation navigation".
- The drawer is opened by the bar's menu button and closed by Escape, the backdrop or the close button. Focus moves into the drawer on open and back to the button on close, and the runtime that does this is the same wayne-ui-theme.js the static path uses.
- The close button is hidden by the stylesheet where the sidebar is docked, because at that width there is nothing to close.
- AppBar's own nav is display:none below 768px, and the drawer is the only place left to reach it at that width, so this repeats those same links under wsu-sidebar__primary-nav. The stylesheet hides the repeat again once the sidebar docks, where the bar's own copy is back.
Accessibility
- hidden on the nav until opened, so a closed drawer is genuinely out of the tab order (2.4.3).
- tabindex="-1" so focus can be moved to the drawer itself.
- The list is labeled by its heading with aria-labelledby.
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 |
|---|---|---|
id | 'app-sidebar' | Must match the bar's drawer target. |
label | config('wayne-ui.sidebar.label') | Names the landmark. |
items | config('wayne-ui.sidebar.items') | Flat items, or groups with a heading. One level of nesting. |
backdrop | true | Renders the drawer backdrop element. Set false where the page supplies its own. |
nav | config('wayne-ui.nav') | AppBar's own items, repeated here for the widths at which the bar hides them. |
navLabel | 'Primary' | Names the repeated list. There is no heading to point aria-labelledby at. |
Vue
| Prop | Type and default | What it does |
|---|---|---|
id | String, 'app-sidebar' | Must match AppBar's drawer prop. |
label | String, 'Sections' | Names the landmark. |
closeLabel | String, 'Close navigation menu' | Accessible name for the drawer close button. |
primaryNavLabel | String, 'Primary' | Names the primary-nav slot's list. |
SidebarNav items | Array, [] | [{ label, href, icon, current, children }]. |
SidebarNav linkAs | String, 'a' | Inertia Link or RouterLink. |
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-sidebar.
| Template | Elements | Accessibility attributes | Uses |
|---|---|---|---|
| internal apps/examples/static-html/index.html | nav | aria-labelhiddentabindex | 1 |
| public apps/examples/static-html/public-app.html | nav | aria-labelhiddentabindex | 1 |