Nav Bar

@sushindustries/ui

A site header whose panels expand, built on <details> so it works before hydration.

9 min read
View as Markdown

The header at the top of this page is this component. It is fed by a Markdown file, and nothing about which site it is in is written in the component.

Viewport width
Desktop100%whatever the page has
tanstacktanstack add https://adamjurek.com/r/tanstack/nav-bar.jsonshadcnpnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/nav-bar.json

Why <details> and not state

A nav is the first thing a reader touches, and often they touch it before hydration has finished. A menu driven by useState is inert until then: it looks interactive, and the first tap does nothing.

<details> opens on click and on Enter, is announced to a screen reader as expandable, and closes on Escape. All of that is the browser's, so it works on the server's first paint.

function closeOnLeave(event: React.FocusEvent<HTMLDetailsElement>): void {
	if (event.currentTarget.contains(event.relatedTarget)) return;
	event.currentTarget.removeAttribute("open");
}

<details className="nav-menu" onBlur={closeOnLeave}>
	<summary className="nav-link">Components</summary>
</details>;
tsx

What it does not have is close-on-outside-click. That comes back as one onBlur handler that removes the open attribute when focus leaves the group. If that handler never runs, the menu stays open until you press the trigger again, which is mildly annoying rather than broken. That is the trade: the failure mode of the JavaScript half is an inconvenience, not a dead control.

Install

tanstack add https://adamjurek.com/r/tanstack/nav-bar.json
shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/nav-bar.json
shell
pnpm add @sushindustries/ui @sushindustries/atoms
shell

What you get

Version0.1.0
Categorylayout · Page structure
Filesnav-bar.tsx, icon.tsx
DependenciesNone
Tagsblock, navigation, no-js, responsive

No runtime dependencies

It brings nothing with it beyond the stylesheet.