Navbar
The <x-wirekit:: component creates a responsive top navigation bar with brand area, navigation items, and action slots. It automatically collapses into a hamburger menu below the md breakpoint for mobile devices.
Building a signed-in dashboard?
navbaris the top-nav shell for marketing sites and top-nav apps. For a sidebar dashboard layout use App Shell instead — and don't nestnavbarinside the header of an app-shell that has asidebarorrailslot, since both would then render their own mobile menu.
Usage
In a real Laravel app, wrap the brand content in an <a href="/"> and bind each item's :active prop to request()->is('dashboard') (or similar) so the current page is highlighted automatically.
Variants
Default
The default variant has a subtle bottom border:
Bordered
Adds a visible border on all sides with a slight shadow:
Transparent
No background, no border -- suitable for hero sections or overlaying images:
Sticky Navbar
Pin the navbar to the top of the viewport on scroll:
<x-wirekit::navbar :sticky="true">
...
</x-wirekit::navbar>
This applies position: sticky; top: 0; and z-index: var(--z-wk-chrome) — 55, which is ABOVE the dropdown layer (50) on purpose. A popover opened in page content is a teleported panel at that layer and would otherwise paint over the bar; a panel anchored inside the bar opens downward and never overlaps it, so nothing is lost, and a modal is still above. The surface is --color-wk-bg-elevated, opaque — there is no backdrop-filter.
An element that takes the focus, or that a fragment link brings into view, comes to rest below a sticky navbar rather than under it: the bar tells the page how much of the top it covers (keeping the focus clear of a pinned bar).
Brand Slot
The brand slot renders on the left side and is always visible (both desktop and mobile):
Active Items
Mark the currently active navigation item. Active items receive accent-colored text and an underline indicator, with aria-current="page" set automatically:
In your Blade templates, use dynamic active detection:
<x-wirekit::navbar.item href="/dashboard" :active="request()->is('dashboard')">
Dashboard
</x-wirekit::navbar.item>
Mobile Behavior
Below the md breakpoint (768px), navigation items collapse behind a hamburger button. The hamburger button:
- Uses the
menuicon (three horizontal lines) - Toggles
aria-expandedbetween"true"and"false" - Has
aria-label="Toggle navigation"for screen readers - Reveals a vertical dropdown panel with all navigation items
The preview above uses :force-mobile="true" to render the mobile layout unconditionally — the hamburger button is always visible, the desktop item row is always hidden. This is purely a preview/testing aid so the mobile state can be demonstrated without resizing the browser. In a real app, omit force-mobile and the navbar will collapse automatically below the md breakpoint (768px). Click the hamburger button to open the mobile menu.
No additional markup is needed for the responsive behavior — the breakpoint-based mobile menu is fully automatic.
A row that scrolls instead of a menu
Some bars are switched between rather than opened: an operator jumps from screen to screen and
wants to see where they are. mobile="scroll" keeps the entries as one row at every width. On a
narrow screen the row scrolls sideways, and the entry for the current page (an active item,
which carries aria-current="page") is brought into view when the page loads and whenever the
row changes width. There is no menu toggle in this mode.
density="compact" sets the entries in the small text size with less padding and draws the ones
you are not on in the muted color, so in a long row the current entry is the one that stands out.
Every navbar.item inside reads it from the bar.
The row scrolls by touch, wheel and trackpad, and a keyboard reaches every entry with Tab, which
scrolls each into view as it takes focus. It draws no scrollbar: the row is the bar, and a track
under it would read as a second rule. Instead, an edge with more entries behind it fades out, and
an edge the row rests against stays sharp, so the row reads as one that scrolls. The fade is the
same edge mask Scroll Area uses, and it is dropped while a link in the
row has keyboard focus. The current entry is brought in by moving the row alone, so a page loaded
half-way down stays where it is.
With Dropdown Submenus
For navigation items with dropdown sub-menus, combine the navbar with Dropdown. Use a ghost button as the trigger and include a chevron icon so users recognize the item has a submenu:
Multiple Dropdown Menus
For a navbar with several dropdown sections, repeat the pattern for each submenu. Each chevron rotates downward to signal an expandable menu:
For rich mega-menu panels with multiple columns and descriptions, see the Navigation Menu component.
Overflow Behavior
When there are too many navigation items, they collapse into the mobile hamburger menu below the md breakpoint (768px). On desktop, items wrap naturally. To keep the bar on a single line, move the rarely-used items into a "More" dropdown — the example below shows four primary items followed by six more tucked behind the dropdown trigger, which is a scalable pattern even when a marketing site accumulates a long list of pages.
For navigation trees beyond ten items or grouped categories, consider upgrading to the Navigation Menu component — it ships flyout panels that can render multi-column mega-menus with headings and descriptions.
Width & Layout
The navbar stretches to full width, and its content does too by default — container is false. Set container to wrap the inner row in a max-width column so the bar's chrome stays edge-to-edge while its content lines up with a container-wrapped body; max then picks the tier. The width comes from the --size-wk-container-* tokens, the same ones the container component reads — not from a max-w-7xl utility, which the component emits nowhere.
Behavior
- Auto-collapse: navigation items are hidden behind a hamburger below the
mdbreakpoint (768px) - Brand and actions are always visible on both desktop and mobile
- Sticky mode uses
position: stickyat--z-wk-chrome, on the opaque--color-wk-bg-elevatedsurface the bar always carries. Nobackdrop-filter— content scrolls behind it, not through it - Livewire SPA navigation (
wire:navigate) automatically closes the mobile menu - Transitions use height animation for smooth mobile menu reveal
Props
<x-wirekit::navbar>
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
string |
'default' |
Visual style: 'default', 'bordered', 'transparent' |
sticky |
bool |
false |
Pin to top of viewport on scroll |
forceMobile |
bool |
false |
Render the mobile layout unconditionally (hamburger visible, desktop item row hidden). Useful for previews and embedded contexts narrower than the md breakpoint. No effect with mobile="scroll", which has one layout at every width |
mobile |
'menu' | 'scroll' |
'menu' |
What the entries do below md. menu folds them behind a toggle; scroll keeps them one sideways-scrolling row and brings the current entry into view. See A row that scrolls instead of a menu |
density |
'default' | 'compact' |
'default' |
How tightly the entries sit. compact sets them in the small text size with less padding and mutes the ones you are not on; every navbar.item inside reads it |
container |
bool |
false |
Wrap the inner row in a max-width container so the nav content aligns with page content instead of spanning the full viewport width. |
max |
string|null |
null |
Container tier when container is set — 'sm', 'md', 'lg', 'xl', '2xl', 'full' (reads --size-wk-container-*). |
scope |
string|null |
null |
Scoped personalization key |
<x-wirekit::navbar.item>
| Prop | Type | Default | Description |
|---|---|---|---|
href |
string |
'#' |
Link destination |
active |
bool |
false |
Highlights as current page |
scope |
string|null |
null |
Scoped personalization key |
Sub-Components
| Component | Purpose |
|---|---|
navbar.item |
A single navigation link |
Named Slots
| Slot | Purpose |
|---|---|
brand |
Logo or brand name (left side, always visible) |
actions |
CTA buttons or user actions (right side, always visible) |
| default | Navigation items (collapse on mobile) |
Accessibility
- Navbar wrapper: a
<nav>landmark namedMain navigation(translated with the app locale) - Active item:
aria-current="page" - Hamburger button:
aria-label="Toggle navigation",aria-expanded(dynamic),aria-controls(linked to collapsible menu ID) - Mobile menu:
id="wk-navbar-mobile", or<your-id>-mobilewhen you give the navbar anid. A second navbar on the same page getswk-navbar-mobile-2, so each hamburger'saria-controlskeeps pointing at its own menu. The menu is shown and hidden by Alpine (x-show), not by thehiddenattribute — so it is absent from the accessibility tree while collapsed either way, but a stylesheet cannot force it open by overriding[hidden] - All items are standard
<a>elements -- fully accessible by default - Focus ring visible on all focusable elements via
focus-visible:ring-[length:var(--ring-wk-width)]
Keyboard Interaction
The navbar is a layout wrapper. Its links and buttons, the mobile-menu toggle among them, keep their own keys, and the bar adds Escape for the mobile menu:
| Key | Action |
|---|---|
| Tab | Move focus through navbar items sequentially |
| Enter / Space | Activate the focused link, or open and close the mobile menu from its button. Opening moves focus to the menu's first entry, so the next Tab goes on through the menu |
| Escape | Close the mobile menu when it is open. Focus inside the menu moves to the menu button |
Pitfalls
- Don't put a
<x-wirekit::inside adropdown> <x-wirekit::. The dropdown's portal-based rendering escapes the navbar's flex context and the trigger's keyboard handlers conflict with the navbar item's. Usenavbar. item> <x-wirekit::for nested navigation.navigation-menu> - Don't render a navbar without a
<x-wirekit::for screen readers. The brand acts as the page-level wayfinding landmark — without it, the navbar'sbrand> <nav aria-label="Main">is unmoored.
Design Tokens
| Element | Token |
|---|---|
| Bar background | --color-wk-bg-elevated |
| Bar border | --color-wk-border / --border-wk-width |
| Bar shadow (bordered variant) | --shadow-wk-sm |
| Bar padding | --padding-wk-x-lg |
| Item text | --color-wk-text |
| Item hover background | --color-wk-bg-hover |
| Item active text | --color-wk-accent-text |
| Mobile menu background | --color-wk-bg-elevated |
| Mobile menu border | --color-wk-border |
| Hamburger icon color | --color-wk-text |
| Font family | --font-wk-sans |
| Font size | --text-wk-md |
| Transition | --transition-wk-duration |
| Bar surface (sticky and not) | --color-wk-bg-elevated, opaque |
| Sticky stacking layer | --z-wk-chrome |
Personalization
Override defaults in config/:
'components' => [
'navbar' => [
'variant' => 'bordered',
'sticky' => true,
],
],
Scoped Personalization
<x-wirekit::navbar scope="marketing">
...
</x-wirekit::navbar>
// In a service provider's boot() method. A closure receives the shipped classes, so the
// component keeps them; a plain string would replace them outright.
WireKit::scope('marketing', [
'navbar' => ['classes' => [
'base' => fn (string $vendor): string => $vendor.' max-w-7xl mx-auto',
]],
]);
Usage & Conventions
Prop conventions — this component uses one or more of the shared semantic prop names (
intent/variant/tone/surface). See Prop naming conventions for the canonical vocabulary, alias matrix, and decision tree.
Further Reading
- WAI-ARIA Navigation Landmark -- semantic navigation regions
- MDN:
<nav>element - MDN:
aria-current - MDN: Responsive design -- mobile-first patterns
- WebAIM: Skip Navigation Links -- consider adding a skip-to-content link before the navbar