Work in progress
— Block components are currently being reworked. APIs and markup may change.
Navigation
Responsive site navigation block with desktop dropdown menus and a mobile drawer using Dialog + Disclosure.
Examples
Default preview
1<div class="overflow-hidden rounded-default border border-border">
2 {{ partial:components/blocks/navigation/simple }}
3</div>
<div class="overflow-hidden rounded-default border border-border">
{{ partial:components/blocks/navigation/simple }}
</div>
Source
1{{#
2 @name Navigation (Simple)
3 @desc Responsive navigation block with desktop dropdowns and mobile drawer/disclosure navigation.
4 @param none string - This block exposes no public props/slots API. Copy-edit commented sections inline.
5 @example {{ partial:components/blocks/navigation/simple }}
6#}}
7{{ _nav_handle = 'main' }}
8{{ _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' }}
9{{ _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' }}
10{{ _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' }}
11<header class="border-border bg-background border-b">
12 {{ partial:components/primitives/container }}
13 <div class="flex h-16 items-center justify-between gap-4">
14 {{# ===== Logo ===== #}}
15 <a href="/" class="text-foreground text-lg font-semibold tracking-tight">Kern</a>
16
17 {{# ===== Desktop Nav ===== #}}
18 <nav class="hidden flex-1 items-center justify-end gap-2 lg:flex" role="navigation" aria-label="Main">
19 <div class="flex items-center gap-1">
20 {{ nav :handle="_nav_handle" max_depth="2" }}
21 {{ if children }}
22 {{ 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" }}
23 {{ slot:trigger }}
24 {{ title }}
25 {{ /slot:trigger }}
26 {{ slot:menu }}
27 {{ if url }}
28 <a
29 href="{{ url }}"
30 role="menuitem"
31 class="{{ _desktop_menu_item_classes }}"
32 {{ if is_current }}aria-current="page"{{ /if }}
33 >
34 Overview
35 </a>
36 <div class="bg-border my-1 h-px" role="separator" aria-hidden="true"></div>
37 {{ /if }}
38 {{ children }}
39 <a
40 href="{{ url }}"
41 role="menuitem"
42 class="{{ _desktop_menu_item_classes }}"
43 {{ if is_current }}aria-current="page"{{ /if }}
44 >
45 {{ title }}
46 </a>
47 {{ /children }}
48 {{ /slot:menu }}
49 {{ /partial:components/primitives/dropdown }}
50 {{ else }}
51 <a
52 href="{{ url }}"
53 class="{{ _desktop_link_classes }}"
54 {{ if is_current }}aria-current="page"{{ /if }}
55 >
56 {{ title }}
57 </a>
58 {{ /if }}
59 {{ /nav }}
60 </div>
61
62 {{# ===== CTA ===== #}}
63 {{ partial:components/primitives/button label="Get started" href="/getting-started" size="sm" }}
64 </nav>
65
66 {{# ===== Mobile Hamburger ===== #}}
67 <div class="lg:hidden">
68 {{ 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" }}
69 {{ slot:trigger }}
70 {{ svg src="icons/menu" class="size-5" }}
71 <span class="sr-only">Open main menu</span>
72 {{ /slot:trigger }}
73 {{ slot:close }}
74 <button
75 type="button"
76 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"
77 aria-label="Close main menu"
78 @click="closeDialog()"
79 >
80 {{ svg src="icons/close" class="size-5" }}
81 </button>
82 {{ /slot:close }}
83 {{# ===== Mobile Drawer ===== #}}
84 <div class="flex h-full flex-col">
85 <div class="border-border border-b px-5 py-4">
86 <a
87 href="/"
88 class="text-foreground text-lg font-semibold tracking-tight"
89 @click="closeDialog()"
90 >
91 Kern
92 </a>
93 </div>
94
95 <nav class="flex-1 overflow-y-auto px-4 py-5" role="navigation" aria-label="Main">
96 <ul class="space-y-2">
97 {{ nav :handle="_nav_handle" max_depth="2" }}
98 <li>
99 {{ if children }}
100 {{ _disclosure_open = 'false' }}
101 {{ if is_parent || is_current }}
102 {{ _disclosure_open = 'true' }}
103 {{ /if }}
104 {{ partial:components/primitives/disclosure class="border-border/70 bg-background/70" open="{_disclosure_open}" }}
105 {{ slot:trigger }}
106 <span>{{ title }}</span>
107 {{ /slot:trigger }}
108 {{ slot:panel }}
109 <div class="space-y-1">
110 {{ if url }}
111 <a
112 href="{{ url }}"
113 class="{{ _mobile_link_classes }}"
114 {{ if is_current }}aria-current="page"{{ /if }}
115 @click="closeDialog()"
116 >
117 Overview
118 </a>
119 {{ /if }}
120 {{ children }}
121 <a
122 href="{{ url }}"
123 class="{{ _mobile_link_classes }}"
124 {{ if is_current }}aria-current="page"{{ /if }}
125 @click="closeDialog()"
126 >
127 {{ title }}
128 </a>
129 {{ /children }}
130 </div>
131 {{ /slot:panel }}
132 {{ /partial:components/primitives/disclosure }}
133 {{ else }}
134 <a
135 href="{{ url }}"
136 class="{{ _mobile_link_classes }}"
137 {{ if is_current }}aria-current="page"{{ /if }}
138 @click="closeDialog()"
139 >
140 {{ title }}
141 </a>
142 {{ /if }}
143 </li>
144 {{ /nav }}
145 </ul>
146 </nav>
147
148 <div class="border-border border-t p-5">
149 {{ partial:components/primitives/button label="Get started" href="/getting-started" class="w-full justify-center" }}
150 </div>
151 </div>
152 {{ /partial:components/primitives/dialog }}
153 </div>
154 </div>
155 {{ /partial:components/primitives/container }}
156</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
1composer require marcorieser/tailwind-merge-statamic
composer require marcorieser/tailwind-merge-statamic
@alpinejs/anchor
1npm install @alpinejs/anchor
npm install @alpinejs/anchor
@alpinejs/collapse
1npm install @alpinejs/collapse
npm install @alpinejs/collapse
@alpinejs/focus
1npm install @alpinejs/focus
npm install @alpinejs/focus