Sidebar nav
The links inside a Sidebar: one heading's worth, or several, with one level of nesting.
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>
<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><x-wsu::sidebar-nav :items="[
['heading' => 'Registration', 'items' => [
['label' => 'Select courses', 'url' => '/courses', 'route' => 'courses.*', 'icon' => 'clipboard-list'],
['label' => 'My schedule', 'url' => '/schedule', 'route' => 'schedule.*', 'icon' => 'calendar'],
]],
]" /><SidebarNav
heading="Registration"
:items="[
{ label: 'Select courses', href: '/courses', icon: 'clipboard-list', current: true },
{ label: 'My schedule', href: '/schedule', icon: 'calendar' },
]"
/>Why it works this way
- Each list is associated with its heading through aria-labelledby rather than aria-label, so the group's name is a real, visible element as well as an accessibility-tree fact: a sighted screen-magnifier user gets the same grouping everyone else does. 1.x rendered the heading as a plain li inside the list, which made the heading itself sound like a menu item.
- The current page is aria-current="page", and the stylesheet keys its highlight off that attribute rather than a class. 1.x emitted class="active" and nothing else, so the current section was conveyed by color alone; styling off the accessible state means the visible and announced facts cannot drift apart.
- items accepts a flat list for one heading, or a list of { heading, items } groups for several, decided by whether any entry carries a heading rather than by a separate prop the caller has to remember to set. HandleWayneUi hands an Inertia layout the whole sidebar this way, in the single config('wayne-ui.sidebar.items') value, and the Inertia guide's own layout renders that whole prop through one SidebarNav rather than a loop.
- One level of nesting only, under children or items, config's own name for the same thing. The stylesheet only styles wsu-sidebar__sublist one level deep, and a sidebar that needs a third level is one that needs rethinking rather than more CSS.
Accessibility
- The group heading is a real element referenced by aria-labelledby on the list, never a string passed to aria-label, so the name is visible as well as announced (1.3.1).
- aria-current="page" on the current link, styled off that attribute rather than a class, so the highlighted item and the announced one are the same fact (4.1.2).
- linkAs on the Vue path accepts an Inertia Link or a RouterLink, so client-side navigation keeps working without this component taking a dependency on either router.
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 |
|---|---|---|
items | [] | Flat [{ label, url, route, icon }], or grouped [{ heading, items }]. Renders nothing when empty. |
level | 0 | Set by the component itself when it recurses into a sublist; not normally passed by hand. |
Vue
| Prop | Type and default | What it does |
|---|---|---|
heading | String, '' | Ignored when items is already grouped. |
items | Array, [] | [{ label, href, icon, current, children }] for one heading, or [{ heading, items }] for several. |
linkAs | String, 'a' | Inertia Link or RouterLink. |