UI componentsdocs/ui/components/navigation.md

Navigation

Move between pages, sections and steps.

← Component reference

Responsive navbar with nested menus (hover or click), overflow scrolling and a mobile drawer (focus trap, scroll lock). Nested lists take the list / item / link slots.

Acme
function Example() {
    const ctrl = createNavbarController();
    effect(() => () => ctrl.dispose());
    return (
        <Navbar
            ctrl={ctrl}
            label="Main"
            brand={<strong>Acme</strong>}
            items={[
                { id: "product", label: "Product", active: true },
                {
                    id: "resources",
                    label: "Resources",
                    children: [
                        { id: "docs", label: "Docs", href: "#docs" },
                        { id: "blog", label: "Blog", href: "#blog" },
                    ],
                },
                { id: "pricing", label: "Pricing", href: "#pricing" },
            ]}
        />
    );
}

Slots: root backdrop brand burger close desktop end header item link list mobile panel scroll shell title track viewport

PropTypeRequiredDefaultDescription
ctrlNavbarControlleryesPass a stable controller from createNavbarController() (required for multiple navbars).
itemsNavMenuItem[]yesTop-level items; items with children open submenus.
brandcontentLogo or name at the start of the bar.
endcontentContent at the end of the bar, e.g. buttons.
labelstring"Primary"Accessible name for the desktop <nav> (default "Primary").
placementNavbarPlacement"static"Pin the bar to the top of the scrollport / viewport. Default: static
triggerNavMenuTrigger"hover"Desktop submenu open mode. Mobile always uses click. Default: hover

NavMenuItem

FieldTypeRequiredDescription
idstringyesItem id; used by the controller to track open submenus.
labelstringyesMenu text.
hrefstringLink target (renders a link).
activebooleanMarks the current page (aria-current).
disabledbooleanShown but can't be chosen.
onSelect() => voidCalled when the item is chosen.
childrenNavMenuItem[]Nested items, shown as a submenu.

NavMenuTrigger

type NavMenuTrigger = "hover" | "click";

NavbarPlacement

type NavbarPlacement = "static" | "sticky" | "fixed";

NavbarController

FieldTypeRequiredDescription
openPath() => string[]yesIds of the open submenus, outermost first.
mobileOpen() => booleanyesWhether the mobile menu is expanded.
isOpen(id: string) => booleanyesWhether the submenu with this id is open.
openTo(path: string[]) => voidyesOpens the submenus along path (and closes the others).
toggle(path: string[]) => voidyesOpens or closes the submenu at the end of path.
closeAll() => voidyesCloses every submenu.
scheduleClose() => voidyesCloses the submenus after a short delay (hover intent).
cancelClose() => voidyesCancels a pending scheduleClose.
setMobileOpen(open: boolean) => voidyesExpands or collapses the mobile menu.
toggleMobile() => voidyesToggles the mobile menu.
dispose() => voidyesClears timers; call when the navbar is removed.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Parts

Standalone navbar link (renders <a> when href is set).

<NavbarLink href="#pricing" active>
    Pricing
</NavbarLink>
PropTypeRequiredDefaultDescription
activebooleanMarks the current page (aria-current).
childrencontentLink text.
hrefstringLink target.
onClick(e: MouseEvent) => voidClick handler (renders a button when there is no href).

Vertical navigation lists.

SidebarNav #

Vertical nav list.

const sections = [
    { id: "overview", label: "Overview" },
    { id: "deploys", label: "Deploys" },
    { id: "settings", label: "Settings" },
];

function Example() {
    const page = signal("deploys");
    return <SidebarNav label="Project" items={sections} value={page()} onChange={page.set} />;
}

Slots: root link

PropTypeRequiredDefaultDescription
itemsSidebarItem[]yesNavigation items, in order.
labelstring"Sidebar"Accessible name for the nav (default "Sidebar").
onChange(id: string) => voidCalled with the id of the item the user picks.
valuestringId of the current item (marked aria-current).

SidebarItem

FieldTypeRequiredDescription
idstringyesItem id, passed to onChange and matched against value.
labelstringyesItem text.
onSelect() => voidCalled when this item is chosen (in addition to onChange).
disabledbooleanShown but can't be chosen.

Navigation row (button, or <a> with href).

<NavLink label="Deploys" description="History and logs" href="#deploys" active />

Slots: root description label left main right

PropTypeRequiredDefaultDescription
labelstringyesLink text.
activebooleanMarks the current page (aria-current).
descriptionstringSecondary line under the label.
disabledbooleanShown but can't be followed.
hrefstringRender as a real link (middle-click, open in new tab, crawlable). Fixed at mount.
leftSectioncontentLeading content, e.g. an icon.
onClick(e: MouseEvent) => voidClick handler (renders a button when there is no href).
rightSectioncontentTrailing content, e.g. a badge or chevron.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Trail of links; the last item is the current page. Links render as <a> when href is set.

<Breadcrumb
    items={[
        { label: "Projects", href: "#projects" },
        { label: "marketing-site", href: "#marketing-site" },
        { label: "Deploys" },
    ]}
/>

Slots: root current item link list separator

PropTypeRequiredDefaultDescription
itemsBreadcrumbItem[]yesCrumbs from the root to the current page (last item).
separatorcontent"/"Separator node (default /).

BreadcrumbItem

FieldTypeRequiredDescription
labelcontentyesCrumb text or content.
hrefstringLink target; without href or onClick the crumb is plain text (the current page).
onClick(e: MouseEvent) => voidClick handler (renders a button when there is no href).
iconcontentLeading icon or content.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Tabs #

WAI-ARIA tabs: roving tabindex, ← → Home End, linked panels, animated indicator (transform only), overflow scroll buttons.

Every push creates a deploy.

function Example() {
    const tab = signal("deploys");
    return (
        <Tabs
            label="Project"
            value={tab()}
            onChange={tab.set}
            items={[
                { id: "overview", label: "Overview", panel: <Text>Traffic and status at a glance.</Text> },
                {
                    id: "deploys",
                    label: "Deploys",
                    badge: "12",
                    panel: <Text>Every push creates a deploy.</Text>,
                },
                { id: "settings", label: "Settings", panel: <Text>Domains, builds and access.</Text> },
            ]}
        />
    );
}

Slots: root badge icon indicator list panel scroll tab viewport

PropTypeRequiredDefaultDescription
itemsTabItem[]yesThe tabs, in order.
onChange(id: string) => voidyesCalled with the id of the tab the user selects.
valuestringyesId of the selected tab.
activation"auto" | "manual""auto"auto selects on arrow focus (default); manual waits for Enter/Space.
growbooleanStretch tabs to fill the row.
labelstringAccessible name for the tablist.
size"sm" | "md" | "lg""md"Tab height and text size.
variant"line" | "pills" | "enclosed" | "segmented""line"line (underline), pills, enclosed (card tabs) or segmented.

TabItem

FieldTypeRequiredDescription
idstringyesTab id, passed to onChange and matched against value.
labelcontentyesTab label (text or content).
disabledbooleanShown but can't be selected; skipped by arrow keys.
iconcontentLeading icon or content.
badgecontentTrailing content, e.g. a count badge.
panelcontentPanel content; when any item has one, Tabs renders linked tabpanels.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Steps #

Step indicator.

function Example() {
    const step = signal("shipping");
    return (
        <Steps
            label="Checkout"
            items={[
                { id: "cart", label: "Cart" },
                { id: "shipping", label: "Shipping", description: "Address and method" },
                { id: "payment", label: "Payment" },
            ]}
            value={step()}
            onChange={step.set}
        />
    );
}

Slots: root button copy description index label step

PropTypeRequiredDefaultDescription
itemsStepItem[]yesThe steps, in order; those before value show as complete.
valuestringyesCurrent step id (active). Prior steps are complete.
labelstring"Progress"Accessible name (default "Progress").
onChange(id: string) => voidMakes steps clickable; called with the step id. Without it, steps are plain text.

StepItem

FieldTypeRequiredDescription
idstringyesStep id, passed to onChange and matched against value.
labelstringyesStep name.
descriptionstringSecondary line under the label.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Pagination #

Move through pages of content.

Pagination #

Page navigation with previous/next controls and numbered pages (variant="simple" shows a status instead). Collapses to arrows on narrow screens.

function Example() {
    const page = signal(3);
    return <Pagination page={page()} pageCount={12} onChange={page.set} />;
}

Slots: root control ellipsis page pages status

PropTypeRequiredDefaultDescription
onChange(page: number) => voidyesCalled with the page the user picks.
pagenumberyesCurrent page, starting at 1.
pageCountnumberyesTotal number of pages.
nextLabelcontentContent of the next-page button (default: an arrow with an accessible "Next page" label).
previousLabelcontentContent of the previous-page button (default: an arrow with an accessible "Previous page" label).
siblingsnumber1Pages shown on each side of the current page (default 1).
variant"pages" | "simple""pages"simple = prev/next + status; pages = numbered buttons (default).

DotPagination #

Dot indicators for carousels and slides.

Slide 2 of 5

function Example() {
    const slide = signal(1);
    return (
        <Stack gap="0.5rem">
            <DotPagination count={5} value={slide()} onChange={slide.set} />
            <Text muted>Slide {slide() + 1} of 5</Text>
        </Stack>
    );
}

Slots: root dot

PropTypeRequiredDefaultDescription
countnumberyesNumber of dots (pages or slides).
onChange(index: number) => voidyesCalled with the index the user picks.
valuenumberyesIndex of the current dot (0-based).
labelstring"Pagination"Accessible name (default "Pagination").

NextPrev #

Previous / next navigation pair.

Reading: Theming

function Example() {
    const pages = ["Installation", "Theming", "Customization", "Accessibility"];
    const page = signal(1);
    return (
        <Stack gap="0.5rem">
            <Text>
                Reading: <strong>{pages[page()]}</strong>
            </Text>
            <NextPrev
                prevLabel={pages[page() - 1] ?? "Start"}
                nextLabel={pages[page() + 1] ?? "End"}
                prevDisabled={page() === 0}
                nextDisabled={page() === pages.length - 1}
                onPrev={() => page.set(page() - 1)}
                onNext={() => page.set(page() + 1)}
            />
        </Stack>
    );
}

Slots: root next prev

PropTypeRequiredDefaultDescription
nextDisabledbooleanDisables the next link (e.g. on the last page).
nextLabelstring"Next"Title of the next page.
onNext() => voidShows the next link; called when it is pressed.
onPrev() => voidShows the previous link; called when it is pressed.
prevDisabledbooleanDisables the previous link (e.g. on the first page).
prevLabelstring"Previous"Title of the previous page.

"Back" link with an arrow.

<BackLink href="#projects">
    All projects
</BackLink>

Slots: root icon label

PropTypeRequiredDefaultDescription
childrencontent"Back"Link text.
hrefstringRender as a link.
onClick(e: MouseEvent) => voidClick handler (renders a button when there is no href).

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Secondary and icon navigation.

Compact pill/sub navigation (UIkit subnav).

function Example() {
    const filter = signal("all");
    return (
        <Subnav
            label="Filter"
            value={filter()}
            onChange={filter.set}
            items={[
                { id: "all", label: "All" },
                { id: "production", label: "Production" },
                { id: "preview", label: "Preview" },
            ]}
        />
    );
}

Slots: root item

PropTypeRequiredDefaultDescription
itemsSubnavItem[]yesNavigation items, in order.
onChange(id: string) => voidyesCalled with the id the user picks.
valuestringyesId of the current item.
labelstring"Sub navigation"Accessible name (default "Sub navigation").

SubnavItem

FieldTypeRequiredDescription
idstringyesItem id, passed to onChange and matched against value.
labelcontentyesItem text or content.
disabledbooleanShown but can't be chosen.

Iconnav #

Icon-only navigation.

function Example() {
    const section = signal("home");
    return (
        <Iconnav
            label="Workspace"
            value={section()}
            onChange={section.set}
            items={[
                { id: "home", icon: "home", label: "Home" },
                { id: "alerts", icon: "bell", label: "Alerts" },
                { id: "settings", icon: "settings", label: "Settings" },
            ]}
        />
    );
}

Slots: root item

PropTypeRequiredDefaultDescription
itemsIconnavItem[]yesNavigation items, in order.
labelstring"Icon navigation"Accessible name (default "Icon navigation").
onChange(id: string) => voidCalled with the id the user picks.
valuestringId of the current item.

IconnavItem

FieldTypeRequiredDescription
idstringyesItem id, passed to onChange and matched against value.
iconIconNameyesIcon shown for the item.
labelstringyesAccessible name and tooltip text.

IconName — Material Design Icons path names used by <Icon />.

type IconName = | "check" | "x" | "plus" | "minus" | "search" | "user" | "users" | "settings" | "menu" | "home" | "heart" | "star" | "bell" | "mail" | "calendar" | "clock" | "edit" | "trash" | "copy" | "download" | "upload" | "link" | "external" | "info" | "warning" | "error" | "success" | "chevron-down" | "chevron-up" | "chevron-left" | "chevron-right" | "arrow-left" | "arrow-right" | "eye" | "eye-off" | "eye-outline" | "lock" | "unlock" | "filter" | "more" | "close" | "spinner" | "sun" | "moon" | "play" | "pause" | "refresh" | "share" | "image" | "file" | "folder" | "zap" | "phone" | "git" | "code";

BottomNav #

Mobile tab bar.

function Example() {
    const tab = signal("home");
    return (
        <BottomNav
            label="Primary"
            items={[
                { id: "home", label: "Home", icon: "home" },
                { id: "search", label: "Search", icon: "search" },
                { id: "inbox", label: "Inbox", icon: "bell" },
            ]}
            value={tab()}
            onChange={tab.set}
        />
    );
}

Slots: root icon item label

PropTypeRequiredDefaultDescription
itemsBottomNavItem[]yesNavigation items (3–5 work best).
onChange(id: string) => voidyesCalled with the id the user picks.
valuestringyesId of the current item.
labelstring"Bottom"Accessible name (default "Bottom").

BottomNavItem

FieldTypeRequiredDescription
idstringyesItem id, passed to onChange and matched against value.
labelstringyesItem text under the icon.
iconIconNameItem icon.

IconName — Material Design Icons path names used by <Icon />.

type IconName = | "check" | "x" | "plus" | "minus" | "search" | "user" | "users" | "settings" | "menu" | "home" | "heart" | "star" | "bell" | "mail" | "calendar" | "clock" | "edit" | "trash" | "copy" | "download" | "upload" | "link" | "external" | "info" | "warning" | "error" | "success" | "chevron-down" | "chevron-up" | "chevron-left" | "chevron-right" | "arrow-left" | "arrow-right" | "eye" | "eye-off" | "eye-outline" | "lock" | "unlock" | "filter" | "more" | "close" | "spinner" | "sun" | "moon" | "play" | "pause" | "refresh" | "share" | "image" | "file" | "folder" | "zap" | "phone" | "git" | "code";

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Burger #

Standalone hamburger control (Mantine Burger).

Navigation is closed

function Example() {
    const opened = signal(false);
    return (
        <Group gap="0.75rem">
            <Burger
                opened={opened()}
                label={opened() ? "Close navigation" : "Open navigation"}
                onClick={() => opened.set(!opened())}
            />
            <Text muted>Navigation is {opened() ? "open" : "closed"}</Text>
        </Group>
    );
}
PropTypeRequiredDefaultDescription
labelstringAccessible name (default: "Open menu" / "Close menu" by state).
onClick(e: MouseEvent) => voidCalled when the button is pressed; toggle opened here.
openedbooleanfalseShow the close (×) state instead of the three lines.
size"sm" | "md"Button size.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

On this page #

Links to sections of the current page.

TableOfContents #

"On this page" navigation.

function Example() {
    const current = signal("install");
    const sections = [
        { id: "install", label: "Installation" },
        { id: "usage", label: "Usage" },
        { id: "theming", label: "Theming" },
    ];
    return (
        <TableOfContents
            title="On this page"
            items={sections.map((s) => ({
                ...s,
                active: current() === s.id,
                onSelect: () => current.set(s.id),
            }))}
        />
    );
}

Slots: root link list title

PropTypeRequiredDefaultDescription
itemsTocItem[]yesSections, in page order.
titlestring"On this page"Small heading above the list.

TocItem

FieldTypeRequiredDescription
idstringyesId of the section on the page.
labelstringyesLink text.
activebooleanMarks the current section.
onSelect() => voidCalled when the link is chosen (e.g. to scroll there).

ScrollSpy #

Highlights the section currently in view, in the page or in the sections' scroll container.

Introduction

What Arachne UI is and when to use it.

Installation

Add the package and import the stylesheet.

Usage

Render components and wire their state.

function Example() {
    const sections = [
        { id: "spy-intro", label: "Introduction", text: "What Arachne UI is and when to use it." },
        {
            id: "spy-install",
            label: "Installation",
            text: "Add the package and import the stylesheet.",
        },
        { id: "spy-usage", label: "Usage", text: "Render components and wire their state." },
    ];
    return (
        <Group align="start" gap="1.5rem">
            <ScrollSpy label="Article sections" offset={8} items={sections} />
            <ScrollArea maxHeight="9rem" aria-label="Article">
                <For each={sections}>
                    {(section) => (
                        <section id={section.id} style={{ "min-height": "7rem" }}>
                            <strong>{section.label}</strong>
                            <Text muted>{section.text}</Text>
                        </section>
                    )}
                </For>
            </ScrollArea>
        </Group>
    );
}

Slots: root item

PropTypeRequiredDefaultDescription
itemsScrollSpyItem[]yesSections to track, in page order.
labelstring"On this page"Accessible name (default "On this page").
offsetnumber96Pixels from the top at which a section counts as current.

ScrollSpyItem

FieldTypeRequiredDescription
idstringyesId of the section element on the page.
labelcontentyesLink text.

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.

Jump to content or back to the top.

Visually hidden until focused; jumps to #main by default.

Skip to content

It appears only while focused — keyboard users meet it first on Tab.

function Example() {
    let link: HTMLAnchorElement | undefined;
    return (
        <Box style={{ position: "relative", "padding-top": "3rem" }}>
            <SkipLink
                href="#main"
                ref={(el: HTMLElement) => {
                    link = el as HTMLAnchorElement;
                }}
            >
                Skip to content
            </SkipLink>
            <Group gap="0.75rem">
                <Button size="sm" variant="outline" onClick={() => link?.focus()}>
                    Reveal the skip link
                </Button>
                <Text muted>It appears only while focused — keyboard users meet it first on Tab.</Text>
            </Group>
        </Box>
    );
}
PropTypeRequiredDefaultDescription
childrencontent"Skip to content"Link text.
hrefstring"#main"Target to jump to (id of your main content).

ToTop #

Scroll-to-top control (UIkit totop).

<ToTop offset={-1} />

Slots: root

PropTypeRequiredDefaultDescription
childrencontentButton content (default: an up arrow).
labelstring"Back to top"Accessible name of the button.
offsetnumber320Scroll distance (px) before the button appears (default 320).

Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.