Nav Bar

@sushindustries/ui

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

12 min read
View as MarkdownEdit on GitHub

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
Phone22remthe floor, the narrowest this promises to work at
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
DependenciesNone
Also installsiconIconThe glyph set, generated from a Markdown table where every drawing carries its reason. One component, typed names, no icon font.@sushindustries/ui · content
Tagsblock, navigation, no-js, responsive

No runtime dependencies

It brings nothing with it beyond the stylesheet.

UsesIconIconThe glyph set, generated from a Markdown table where every drawing carries its reason. One component, typed names, no icon font.@sushindustries/ui · content