Navigation
Move between pages, sections and steps.
Navbar #
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.
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
ctrl | NavbarController | yes | Pass a stable controller from createNavbarController() (required for multiple navbars). | |
items | NavMenuItem[] | yes | Top-level items; items with children open submenus. | |
brand | content | Logo or name at the start of the bar. | ||
end | content | Content at the end of the bar, e.g. buttons. | ||
label | string | "Primary" | Accessible name for the desktop <nav> (default "Primary"). | |
placement | NavbarPlacement | "static" | Pin the bar to the top of the scrollport / viewport. Default: static | |
trigger | NavMenuTrigger | "hover" | Desktop submenu open mode. Mobile always uses click. Default: hover |
NavMenuItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Item id; used by the controller to track open submenus. |
label | string | yes | Menu text. |
href | string | Link target (renders a link). | |
active | boolean | Marks the current page (aria-current). | |
disabled | boolean | Shown but can't be chosen. | |
onSelect | () => void | Called when the item is chosen. | |
children | NavMenuItem[] | Nested items, shown as a submenu. |
NavMenuTrigger
type NavMenuTrigger = "hover" | "click";NavbarPlacement
type NavbarPlacement = "static" | "sticky" | "fixed";NavbarController
| Field | Type | Required | Description |
|---|---|---|---|
openPath | () => string[] | yes | Ids of the open submenus, outermost first. |
mobileOpen | () => boolean | yes | Whether the mobile menu is expanded. |
isOpen | (id: string) => boolean | yes | Whether the submenu with this id is open. |
openTo | (path: string[]) => void | yes | Opens the submenus along path (and closes the others). |
toggle | (path: string[]) => void | yes | Opens or closes the submenu at the end of path. |
closeAll | () => void | yes | Closes every submenu. |
scheduleClose | () => void | yes | Closes the submenus after a short delay (hover intent). |
cancelClose | () => void | yes | Cancels a pending scheduleClose. |
setMobileOpen | (open: boolean) => void | yes | Expands or collapses the mobile menu. |
toggleMobile | () => void | yes | Toggles the mobile menu. |
dispose | () => void | yes | Clears timers; call when the navbar is removed. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Parts
NavbarLink #
Standalone navbar link (renders <a> when href is set).
<NavbarLink href="#pricing" active>
Pricing
</NavbarLink>| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
active | boolean | Marks the current page (aria-current). | ||
children | content | Link text. | ||
href | string | Link target. | ||
onClick | (e: MouseEvent) => void | Click handler (renders a button when there is no href). |
Sidebar navigation #
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | SidebarItem[] | yes | Navigation items, in order. | |
label | string | "Sidebar" | Accessible name for the nav (default "Sidebar"). | |
onChange | (id: string) => void | Called with the id of the item the user picks. | ||
value | string | Id of the current item (marked aria-current). |
SidebarItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Item id, passed to onChange and matched against value. |
label | string | yes | Item text. |
onSelect | () => void | Called when this item is chosen (in addition to onChange). | |
disabled | boolean | Shown but can't be chosen. |
NavLink #
Navigation row (button, or <a> with href).
<NavLink label="Deploys" description="History and logs" href="#deploys" active />Slots: root description label left main right
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | yes | Link text. | |
active | boolean | Marks the current page (aria-current). | ||
description | string | Secondary line under the label. | ||
disabled | boolean | Shown but can't be followed. | ||
href | string | Render as a real link (middle-click, open in new tab, crawlable). Fixed at mount. | ||
leftSection | content | Leading content, e.g. an icon. | ||
onClick | (e: MouseEvent) => void | Click handler (renders a button when there is no href). | ||
rightSection | content | Trailing content, e.g. a badge or chevron. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Breadcrumb #
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | BreadcrumbItem[] | yes | Crumbs from the root to the current page (last item). | |
separator | content | "/" | Separator node (default /). |
BreadcrumbItem
| Field | Type | Required | Description |
|---|---|---|---|
label | content | yes | Crumb text or content. |
href | string | Link target; without href or onClick the crumb is plain text (the current page). | |
onClick | (e: MouseEvent) => void | Click handler (renders a button when there is no href). | |
icon | content | Leading 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | TabItem[] | yes | The tabs, in order. | |
onChange | (id: string) => void | yes | Called with the id of the tab the user selects. | |
value | string | yes | Id of the selected tab. | |
activation | "auto" | "manual" | "auto" | auto selects on arrow focus (default); manual waits for Enter/Space. | |
grow | boolean | Stretch tabs to fill the row. | ||
label | string | Accessible 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
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Tab id, passed to onChange and matched against value. |
label | content | yes | Tab label (text or content). |
disabled | boolean | Shown but can't be selected; skipped by arrow keys. | |
icon | content | Leading icon or content. | |
badge | content | Trailing content, e.g. a count badge. | |
panel | content | Panel 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | StepItem[] | yes | The steps, in order; those before value show as complete. | |
value | string | yes | Current step id (active). Prior steps are complete. | |
label | string | "Progress" | Accessible name (default "Progress"). | |
onChange | (id: string) => void | Makes steps clickable; called with the step id. Without it, steps are plain text. |
StepItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Step id, passed to onChange and matched against value. |
label | string | yes | Step name. |
description | string | Secondary 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onChange | (page: number) => void | yes | Called with the page the user picks. | |
page | number | yes | Current page, starting at 1. | |
pageCount | number | yes | Total number of pages. | |
nextLabel | content | Content of the next-page button (default: an arrow with an accessible "Next page" label). | ||
previousLabel | content | Content of the previous-page button (default: an arrow with an accessible "Previous page" label). | ||
siblings | number | 1 | Pages 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
count | number | yes | Number of dots (pages or slides). | |
onChange | (index: number) => void | yes | Called with the index the user picks. | |
value | number | yes | Index of the current dot (0-based). | |
label | string | "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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
nextDisabled | boolean | Disables the next link (e.g. on the last page). | ||
nextLabel | string | "Next" | Title of the next page. | |
onNext | () => void | Shows the next link; called when it is pressed. | ||
onPrev | () => void | Shows the previous link; called when it is pressed. | ||
prevDisabled | boolean | Disables the previous link (e.g. on the first page). | ||
prevLabel | string | "Previous" | Title of the previous page. |
BackLink #
"Back" link with an arrow.
<BackLink href="#projects">
All projects
</BackLink>Slots: root icon label
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | content | "Back" | Link text. | |
href | string | Render as a link. | ||
onClick | (e: MouseEvent) => void | Click handler (renders a button when there is no href). |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Sub-navigation #
Secondary and icon navigation.
Subnav #
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | SubnavItem[] | yes | Navigation items, in order. | |
onChange | (id: string) => void | yes | Called with the id the user picks. | |
value | string | yes | Id of the current item. | |
label | string | "Sub navigation" | Accessible name (default "Sub navigation"). |
SubnavItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Item id, passed to onChange and matched against value. |
label | content | yes | Item text or content. |
disabled | boolean | Shown 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | IconnavItem[] | yes | Navigation items, in order. | |
label | string | "Icon navigation" | Accessible name (default "Icon navigation"). | |
onChange | (id: string) => void | Called with the id the user picks. | ||
value | string | Id of the current item. |
IconnavItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Item id, passed to onChange and matched against value. |
icon | IconName | yes | Icon shown for the item. |
label | string | yes | Accessible 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | BottomNavItem[] | yes | Navigation items (3–5 work best). | |
onChange | (id: string) => void | yes | Called with the id the user picks. | |
value | string | yes | Id of the current item. | |
label | string | "Bottom" | Accessible name (default "Bottom"). |
BottomNavItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Item id, passed to onChange and matched against value. |
label | string | yes | Item text under the icon. |
icon | IconName | Item 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>
);
}| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | Accessible name (default: "Open menu" / "Close menu" by state). | ||
onClick | (e: MouseEvent) => void | Called when the button is pressed; toggle opened here. | ||
opened | boolean | false | Show 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | TocItem[] | yes | Sections, in page order. | |
title | string | "On this page" | Small heading above the list. |
TocItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Id of the section on the page. |
label | string | yes | Link text. |
active | boolean | Marks the current section. | |
onSelect | () => void | Called 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.
What Arachne UI is and when to use it.
Add the package and import the stylesheet.
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | ScrollSpyItem[] | yes | Sections to track, in page order. | |
label | string | "On this page" | Accessible name (default "On this page"). | |
offset | number | 96 | Pixels from the top at which a section counts as current. |
ScrollSpyItem
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Id of the section element on the page. |
label | content | yes | Link text. |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.
Skip link & to top #
Jump to content or back to the top.
SkipLink #
Visually hidden until focused; jumps to #main by default.
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>
);
}| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | content | "Skip to content" | Link text. | |
href | string | "#main" | Target to jump to (id of your main content). |
ToTop #
Scroll-to-top control (UIkit totop).
<ToTop offset={-1} />Slots: root
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | content | Button content (default: an up arrow). | ||
label | string | "Back to top" | Accessible name of the button. | |
offset | number | 320 | Scroll distance (px) before the button appears (default 320). |
Also accepts the shared props: pass-through attributes, class, style, classes, styles, unstyled.