Section Nav
A link-based section sub-navigation strip — leading icon + label + optional count pill, with a static underline on the active item. Copy it into your project and own it completely.
Basic
When to use
Use SectionNav for a sub-navigation strip across the top of a detail view whose sections are real routes — for example an organization's Detail / Roles / Invites screens. Each item is its own URL, so navigation is link-based, and each can surface a count pill (member count, pending invites).
This is structurally distinct from Tabs: Tabs uses button triggers and in-page content panels for switching content on the same route, with no link navigation and no count slot. Reach for SectionNav when each section is a separate page, and Tabs when the panels live on the same route.
Anatomy
Each SectionNavItem renders a leading Phosphor icon, a label, and an optional trailing count pill. The active item (isActive) gets text-primary, a static 2px primary underline, and a primary-tinted count pill — and is marked with aria-current="page" for assistive technology.
next/link navigation (asChild)
By default a SectionNavItem renders an <a> and applies href directly. For client-side navigation, pass asChild and provide a next/link element as the single child — the item's chrome (icon, label, count) is merged onto the rendered anchor.
import Link from 'next/link';
import { UsersIcon, ShieldIcon, EnvelopeIcon } from '@phosphor-icons/react';
import { SectionNav, SectionNavItem } from '@/components/ui/section-nav/section-nav';
export function OrgSectionNav({ active }: { active: 'detail' | 'roles' | 'invites' }) {
return (
<SectionNav aria-label="Organization sections">
<SectionNavItem asChild isActive={active === 'detail'} icon={UsersIcon} label="Detail">
<Link href="/org/detail" />
</SectionNavItem>
<SectionNavItem asChild isActive={active === 'roles'} icon={ShieldIcon} label="Roles" count={4}>
<Link href="/org/roles" />
</SectionNavItem>
<SectionNavItem asChild isActive={active === 'invites'} icon={EnvelopeIcon} label="Invites" count={2}>
<Link href="/org/invites" />
</SectionNavItem>
</SectionNav>
);
}Count pill
Pass a count to render a trailing pill. It is neutral-toned at rest and re-tones to primary on the active item. 0 renders; omit count (or pass undefined) to hide the pill entirely.
<SectionNavItem href="/roles" label="Roles" count={4} /> {/* neutral pill */}
<SectionNavItem href="/roles" label="Roles" count={4} isActive /> {/* primary-tinted pill */}
<SectionNavItem href="/detail" label="Detail" /> {/* no pill */}Installation
npx visor add section-navThis copies two files into your project:
components/ui/section-nav/section-nav.tsx— the componentcomponents/ui/section-nav/section-nav.module.css— the styles
import { SectionNav, SectionNavItem } from '@/components/ui/section-nav/section-nav';API Reference
SectionNav
The root element renders a <nav> with aria-label="section" (override via aria-label) and accepts all standard HTML nav attributes.
SectionNavItem
| Prop | Type | Default | Description |
|---|---|---|---|
label* | React.ReactNode | — | The item's visible label text. |
href | string | — | Destination URL, applied directly when not using asChild. |
icon | Icon | — | Leading Phosphor icon component (e.g. UsersIcon). |
count | number | — | Optional trailing count pill value. 0 is rendered; undefined hides the pill. |
isActive | boolean | false | Marks the item as the current section — text-primary, 2px primary underline, primary-tinted count pill, and aria-current="page". |
asChild | boolean | false | Merge the item's chrome onto the immediate child element instead of rendering an <a>. Use with next/link. |
Accessibility
SectionNavrenders a<nav>landmark witharia-label="section"by default — override it with a more specific label (e.g."Organization sections") when a page has multiple navigation regions.- The active item sets
aria-current="page", communicating the current section to screen readers. - Color is never the sole indicator of the active item: the active state also carries a 2px underline and
aria-current. - Leading icons are decorative (
aria-hidden) — the label carries the accessible name.
Section Header
Compact section-divider primitive with an uppercase title and optional right-aligned meta label. Sized for in-page content sectioning, distinct from the page-level PageHeader.
Stepper
A multi-step progress indicator with horizontal and vertical orientations. Auto-derives step status from activeStep with accessible ARIA roles — copy it into your project and own it completely.