minidocs

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.

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.

On this page
Search in
Type to start searching