Navigation
Responsive site navigation block with desktop dropdown menus and a mobile drawer using Dialog + Disclosure.
Examples
Default preview
<div class="overflow-hidden rounded-default border border-border">
{{ partial:components/blocks/navigation/simple }}
</div>
<div class="overflow-hidden rounded-default border border-border">
{{ partial:components/blocks/navigation/simple }}
</div>
Source
{{#
@name Navigation (Simple)
@desc Responsive navigation block with desktop dropdowns and mobile drawer/disclosure navigation.
@param none string - This block exposes no public props/slots API. Copy-edit commented sections inline.
@example {{ partial:components/blocks/navigation/simple }}
#}}
{{ _nav_handle = 'main' }}
{{ _desktop_link_classes = 'text-foreground hover:bg-secondary/60 focus-visible:ring-ring focus-visible:ring-offset-background aria-[current=page]:text-primary aria-[current=page]:font-semibold inline-flex items-center rounded-default px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2' }}
{{ _desktop_menu_item_classes = 'text-foreground hover:bg-secondary/70 focus-visible:bg-secondary/70 focus-visible:ring-ring aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary flex w-full items-center rounded-sm px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2' }}
{{ _mobile_link_classes = 'text-foreground hover:bg-secondary/70 focus-visible:ring-ring focus-visible:ring-offset-background aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary aria-[current=page]:font-semibold block rounded-default px-3 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2' }}
<header class="border-border bg-background border-b">
{{ partial:components/primitives/container }}
<div class="flex h-16 items-center justify-between gap-4">
{{# ===== Logo ===== #}}
<a href="/" class="text-foreground text-lg font-semibold tracking-tight">Kern</a>
{{# ===== Desktop Nav ===== #}}
<nav class="hidden flex-1 items-center justify-end gap-2 lg:flex" role="navigation" aria-label="Main">
<div class="flex items-center gap-1">
{{ nav :handle="_nav_handle" max_depth="2" }}
{{ if children }}
{{ partial:components/primitives/dropdown class="[&>button]:shadow-none [&>button]:bg-transparent [&>button]:border-transparent [&>button]:text-foreground [&>button]:hover:bg-secondary/60 [&>button]:aria-expanded:bg-secondary/70 [&>button]:px-3 [&>button]:py-2 [&>button]:text-sm [&>button]:font-medium" }}
{{ slot:trigger }}
{{ title }}
{{ /slot:trigger }}
{{ slot:menu }}
{{ if url }}
<a
href="{{ url }}"
role="menuitem"
class="{{ _desktop_menu_item_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
Overview
</a>
<div class="bg-border my-1 h-px" role="separator" aria-hidden="true"></div>
{{ /if }}
{{ children }}
<a
href="{{ url }}"
role="menuitem"
class="{{ _desktop_menu_item_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
{{ title }}
</a>
{{ /children }}
{{ /slot:menu }}
{{ /partial:components/primitives/dropdown }}
{{ else }}
<a
href="{{ url }}"
class="{{ _desktop_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
{{ title }}
</a>
{{ /if }}
{{ /nav }}
</div>
{{# ===== CTA ===== #}}
{{ partial:components/primitives/button label="Get started" href="/getting-started" size="sm" }}
</nav>
{{# ===== Mobile Hamburger ===== #}}
<div class="lg:hidden">
{{ partial:components/primitives/dialog class="my-0 mr-0 ml-auto h-full w-full max-w-sm rounded-none rounded-l-lg border-l border-border bg-background/95 p-0 shadow-xl backdrop-blur-xl" }}
{{ slot:trigger }}
{{ svg src="icons/menu" class="size-5" }}
<span class="sr-only">Open main menu</span>
{{ /slot:trigger }}
{{ slot:close }}
<button
type="button"
class="text-muted-foreground hover:bg-secondary/70 hover:text-foreground focus-visible:ring-ring rounded-default p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
aria-label="Close main menu"
@click="closeDialog()"
>
{{ svg src="icons/close" class="size-5" }}
</button>
{{ /slot:close }}
{{# ===== Mobile Drawer ===== #}}
<div class="flex h-full flex-col">
<div class="border-border border-b px-5 py-4">
<a
href="/"
class="text-foreground text-lg font-semibold tracking-tight"
@click="closeDialog()"
>
Kern
</a>
</div>
<nav class="flex-1 overflow-y-auto px-4 py-5" role="navigation" aria-label="Main">
<ul class="space-y-2">
{{ nav :handle="_nav_handle" max_depth="2" }}
<li>
{{ if children }}
{{ _disclosure_open = 'false' }}
{{ if is_parent || is_current }}
{{ _disclosure_open = 'true' }}
{{ /if }}
{{ partial:components/primitives/disclosure class="border-border/70 bg-background/70" open="{_disclosure_open}" }}
{{ slot:trigger }}
<span>{{ title }}</span>
{{ /slot:trigger }}
{{ slot:panel }}
<div class="space-y-1">
{{ if url }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
Overview
</a>
{{ /if }}
{{ children }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
{{ title }}
</a>
{{ /children }}
</div>
{{ /slot:panel }}
{{ /partial:components/primitives/disclosure }}
{{ else }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
{{ title }}
</a>
{{ /if }}
</li>
{{ /nav }}
</ul>
</nav>
<div class="border-border border-t p-5">
{{ partial:components/primitives/button label="Get started" href="/getting-started" class="w-full justify-center" }}
</div>
</div>
{{ /partial:components/primitives/dialog }}
</div>
</div>
{{ /partial:components/primitives/container }}
</header>
{{#
@name Navigation (Simple)
@desc Responsive navigation block with desktop dropdowns and mobile drawer/disclosure navigation.
@param none string - This block exposes no public props/slots API. Copy-edit commented sections inline.
@example {{ partial:components/blocks/navigation/simple }}
#}}
{{ _nav_handle = 'main' }}
{{ _desktop_link_classes = 'text-foreground hover:bg-secondary/60 focus-visible:ring-ring focus-visible:ring-offset-background aria-[current=page]:text-primary aria-[current=page]:font-semibold inline-flex items-center rounded-default px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2' }}
{{ _desktop_menu_item_classes = 'text-foreground hover:bg-secondary/70 focus-visible:bg-secondary/70 focus-visible:ring-ring aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary flex w-full items-center rounded-sm px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2' }}
{{ _mobile_link_classes = 'text-foreground hover:bg-secondary/70 focus-visible:ring-ring focus-visible:ring-offset-background aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary aria-[current=page]:font-semibold block rounded-default px-3 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2' }}
<header class="border-border bg-background border-b">
{{ partial:components/primitives/container }}
<div class="flex h-16 items-center justify-between gap-4">
{{# ===== Logo ===== #}}
<a href="/" class="text-foreground text-lg font-semibold tracking-tight">Kern</a>
{{# ===== Desktop Nav ===== #}}
<nav class="hidden flex-1 items-center justify-end gap-2 lg:flex" role="navigation" aria-label="Main">
<div class="flex items-center gap-1">
{{ nav :handle="_nav_handle" max_depth="2" }}
{{ if children }}
{{ partial:components/primitives/dropdown class="[&>button]:shadow-none [&>button]:bg-transparent [&>button]:border-transparent [&>button]:text-foreground [&>button]:hover:bg-secondary/60 [&>button]:aria-expanded:bg-secondary/70 [&>button]:px-3 [&>button]:py-2 [&>button]:text-sm [&>button]:font-medium" }}
{{ slot:trigger }}
{{ title }}
{{ /slot:trigger }}
{{ slot:menu }}
{{ if url }}
<a
href="{{ url }}"
role="menuitem"
class="{{ _desktop_menu_item_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
Overview
</a>
<div class="bg-border my-1 h-px" role="separator" aria-hidden="true"></div>
{{ /if }}
{{ children }}
<a
href="{{ url }}"
role="menuitem"
class="{{ _desktop_menu_item_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
{{ title }}
</a>
{{ /children }}
{{ /slot:menu }}
{{ /partial:components/primitives/dropdown }}
{{ else }}
<a
href="{{ url }}"
class="{{ _desktop_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
>
{{ title }}
</a>
{{ /if }}
{{ /nav }}
</div>
{{# ===== CTA ===== #}}
{{ partial:components/primitives/button label="Get started" href="/getting-started" size="sm" }}
</nav>
{{# ===== Mobile Hamburger ===== #}}
<div class="lg:hidden">
{{ partial:components/primitives/dialog class="my-0 mr-0 ml-auto h-full w-full max-w-sm rounded-none rounded-l-lg border-l border-border bg-background/95 p-0 shadow-xl backdrop-blur-xl" }}
{{ slot:trigger }}
{{ svg src="icons/menu" class="size-5" }}
<span class="sr-only">Open main menu</span>
{{ /slot:trigger }}
{{ slot:close }}
<button
type="button"
class="text-muted-foreground hover:bg-secondary/70 hover:text-foreground focus-visible:ring-ring rounded-default p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
aria-label="Close main menu"
@click="closeDialog()"
>
{{ svg src="icons/close" class="size-5" }}
</button>
{{ /slot:close }}
{{# ===== Mobile Drawer ===== #}}
<div class="flex h-full flex-col">
<div class="border-border border-b px-5 py-4">
<a
href="/"
class="text-foreground text-lg font-semibold tracking-tight"
@click="closeDialog()"
>
Kern
</a>
</div>
<nav class="flex-1 overflow-y-auto px-4 py-5" role="navigation" aria-label="Main">
<ul class="space-y-2">
{{ nav :handle="_nav_handle" max_depth="2" }}
<li>
{{ if children }}
{{ _disclosure_open = 'false' }}
{{ if is_parent || is_current }}
{{ _disclosure_open = 'true' }}
{{ /if }}
{{ partial:components/primitives/disclosure class="border-border/70 bg-background/70" open="{_disclosure_open}" }}
{{ slot:trigger }}
<span>{{ title }}</span>
{{ /slot:trigger }}
{{ slot:panel }}
<div class="space-y-1">
{{ if url }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
Overview
</a>
{{ /if }}
{{ children }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
{{ title }}
</a>
{{ /children }}
</div>
{{ /slot:panel }}
{{ /partial:components/primitives/disclosure }}
{{ else }}
<a
href="{{ url }}"
class="{{ _mobile_link_classes }}"
{{ if is_current }}aria-current="page"{{ /if }}
@click="closeDialog()"
>
{{ title }}
</a>
{{ /if }}
</li>
{{ /nav }}
</ul>
</nav>
<div class="border-border border-t p-5">
{{ partial:components/primitives/button label="Get started" href="/getting-started" class="w-full justify-center" }}
</div>
</div>
{{ /partial:components/primitives/dialog }}
</div>
</div>
{{ /partial:components/primitives/container }}
</header>
Customization Notes
Change the nav handle in one line at the top of the block: `_nav_handle = 'main'`.
Desktop and mobile trees are intentionally separate (`hidden lg:flex` + `lg:hidden`).
Keep nested desktop items inside Dropdown and mobile nested items inside Disclosure.
Dependencies
Packages
marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic
@alpinejs/anchor
npm install @alpinejs/anchor
npm install @alpinejs/anchor
@alpinejs/collapse
npm install @alpinejs/collapse
npm install @alpinejs/collapse
@alpinejs/focus
npm install @alpinejs/focus
npm install @alpinejs/focus