Sidebar
You write the sidebar yourself: walk the document tree and render each item with the Sidebar components. The structure, order and look of every item are in your code.
A complete example
---
import { Sidebar } from '@minidocs/astro/components';
import { navState } from '@minidocs/astro/runtime';
import { tree } from '../lib/docs';
const { id } = Astro.props;
const nav = navState(tree, id);
---
<Sidebar.Root id="sidebar" label="Docs">
<Sidebar.List>
{tree.map(function item(node) {
return (
<Sidebar.Item>
{node.children.length > 0 ? (
<Sidebar.Group {...nav.of(node)}>
<Fragment slot="label">{node.label}</Fragment>
<Sidebar.List>{node.children.map(item)}</Sidebar.List>
</Sidebar.Group>
) : (
<Sidebar.Link {...nav.of(node)}>{node.label}</Sidebar.Link>
)}
</Sidebar.Item>
);
})}
</Sidebar.List>
</Sidebar.Root>
The callback of tree.map is named item, so a group with children renders the next level with node.children.map(item), and a tree of any depth renders. On multilingual sites, use tree[locale] instead of tree.
nav.of(node)
navState(tree, id) records where the current page sits in the tree, and nav.of(node) returns what rendering a node needs:
| Field | Meaning |
|---|---|
href |
The URL of a page or external link, none for groups |
current |
Whether this node is the current page |
open |
Whether the current page is this node or below it |
The fields share their names with the components’ props, so they can be spread straight in. You can also override any of them, such as keeping a group always open:
<Sidebar.Group {...nav.of(node)} open>
See Components and Styles for the props of each component.
Kinds of groups
Labels
Groups on the first level are often just a small heading with pages listed below, without collapsing. Use Sidebar.Label with Sidebar.List. This renders labels on the first level and collapsible groups deeper down:
{tree.map(function item(node) {
return (
<Sidebar.Item>
{node.children.length === 0 ? (
<Sidebar.Link {...nav.of(node)}>{node.label}</Sidebar.Link>
) : tree.includes(node) ? (
<>
<Sidebar.Label>{node.label}</Sidebar.Label>
<Sidebar.List>{node.children.map(item)}</Sidebar.List>
</>
) : (
<Sidebar.Group {...nav.of(node)}>
<Fragment slot="label">{node.label}</Fragment>
<Sidebar.List>{node.children.map(item)}</Sidebar.List>
</Sidebar.Group>
)}
</Sidebar.Item>
);
})}
Collapsible groups
Sidebar.Group renders a group that collapses and expands when its title is clicked, as in the complete example above. Its title goes in the label slot.
Accordions
Give groups on the same level the same name, and opening one closes the others. Groups with a page of their own are not included:
<Sidebar.Group {...nav.of(node)} name="sections">
Groups with their own page
A group defined with page(id, children) is also a page, and nav.of(node) gives it an href. Its title behaves differently depending on the current page:
| Current page | Clicking the title |
|---|---|
| Another page | Goes to the group’s page and expands it |
| A page inside the group | Goes to the group’s page |
| The group’s own page | Collapses or expands it |
Keeping state across pages
Going from one page to another, the sidebar stays as it was: open groups and the scroll position do not change. Groups containing the current page are always open, so you can always see where you are.
The menu on narrow screens
Sidebar.Trigger is the button that opens and closes the sidebar, with target set to the sidebar’s id:
<Sidebar.Trigger target="sidebar" aria-label="Menu">☰</Sidebar.Trigger>
<Sidebar.Root id="sidebar" label="Docs">...</Sidebar.Root>
Inside Layout.Sidebar, the default styles turn the sidebar into a drawer that slides in from the left on narrow screens, closing on Esc or a click outside, see Page Layout.