Nasaq
Components

DocsShell

The documentation layout with a tree sidebar, breadcrumb, copy page, callouts, an on this page rail that follows the reader and previous and next links.

PreviewOpen ↗

Code

import { useState } from "react";import { DocsShell } from "@fadymondy/nasaq/web";const nav = [  { id: "intro", title: "Introduction" },  { id: "guides", title: "Guides", children: [{ id: "install", title: "Installation" }] },];const pages = {  intro: { id: "intro", title: "Introduction", markdown: "## Why\n\nText.\n\n> [!TIP]\n> Start small." },  install: { id: "install", title: "Installation", markdown: "## Install\n\nRun `pnpm add`." },};export function Docs() {  const [id, setId] = useState<keyof typeof pages>("intro");  return <DocsShell nav={nav} page={pages[id]} onNavigate={(next) => setId(next as keyof typeof pages)} brand="Nasaq Docs" />;}

Layout · beta

Live examples and controls: DocsShell in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/docs-shell.json

The frame of a documentation site. A top bar, a tree of pages on the side (a drawer on phones) with a filter, and the page itself: breadcrumb, title, a "Copy page" button that puts the page on the clipboard as Markdown, the body with callouts, an "On this page" rail that highlights the heading you are reading, and previous and next links. It shows the page you pass and calls onNavigate; routing is yours.

The body is rendered by the blog's PostBody, and the rail is the blog's TableOfContents with useActiveHeading.

When to use

  • Product documentation, guides and API references written in Markdown.
  • Any multi-page reading experience with a tree of pages.

When not to use

  • A single article: use blog-post.
  • App navigation: use SidebarLayout or AppShell.
  • Terms and privacy pages: use legal-page.

Import

import { DocsShell } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { useState } from "react";
import { DocsShell } from "@fadymondy/nasaq/web";

const nav = [
  { id: "intro", title: "Introduction" },
  { id: "guides", title: "Guides", children: [{ id: "install", title: "Installation" }] },
];
const pages = {
  intro: { id: "intro", title: "Introduction", markdown: "## Why\n\nText.\n\n> [!TIP]\n> Start small." },
  install: { id: "install", title: "Installation", markdown: "## Install\n\nRun `pnpm add`." },
};

export function Docs() {
  const [id, setId] = useState<keyof typeof pages>("intro");
  return <DocsShell nav={nav} page={pages[id]} onNavigate={(next) => setId(next as keyof typeof pages)} brand="Nasaq Docs" />;
}

Anatomy

DocsShell             data-slot="docs-shell"
├─ top bar            brand, menu button (phones), actions
├─ sidebar            data-slot="docs-sidebar"  filter + TreeView (Sheet on phones)
├─ article
│   ├─ Breadcrumb
│   ├─ title + CopyButton
│   ├─ "On this page" (collapsed, below the xl breakpoint)
│   ├─ body           data-slot="docs-body"  PostBody
│   ├─ updated date and edit link
│   └─ previous / next
└─ rail               data-slot="docs-toc"  TableOfContents

API

PropTypeDefaultDescription
navDocsNavNode[]required{ id, title, children?, badge? }. Nodes with children are sections, leaves are pages.
pageDocsPageDatarequired{ id, title, description?, markdown, updated?, editHref? }. Its id is the active item.
onNavigate(id) => voidrequiredA page was picked in the sidebar or the pager.
brand, actionsReactNodenoneStart and end of the top bar.
searchablebooleantrueThe filter box above the tree.
copyPagebooleantrueThe "Copy page" button.
renderBody(page) => ReactNodePostBodyReplace the body, for example with RichMarkdown.
sidebarHeaderReactNodenoneAbove the filter, such as a version picker.
scrollOffsetnumber96Space headings keep from the top when scrolled to.
labelsDocsShellLabelsEnglish or ArabicString overrides.

Helpers

Pure and exported: docsPages, docsTrail, docsAncestorIds, docsPrevNext, filterDocsTree, docsSectionIds, docsPageMarkdown.

Examples

<DocsShell nav={nav} page={page} onNavigate={go} renderBody={(p) => <RichMarkdown>{p.markdown}</RichMarkdown>} />
<DocsShell nav={nav} page={{ ...page, updated: "2026-09-12", editHref: "https://github.com/acme/docs/edit/main/x.md" }} onNavigate={go} actions={<ThemeSwitch />} />

Callouts are written in the Markdown as > [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING] or > [!CAUTION].

Accessibility

KeyAction
Arrow keysMove in the tree. Left and Right expand and collapse (swapped in RTL).
Enter, SpaceOpen the page or toggle a section.
Type a letterJump to the next page starting with it.
TabSidebar, page content, rail.
  • The sidebar is a nav with a tree; the current page is selected. The rail marks the current heading with aria-current="location".
  • The drawer on phones is a modal with a title, and closes after a page is chosen.
  • Filtering shows a message when nothing matches.

RTL & i18n

  • The sidebar sits on the reading start side and the rail on the end side; arrows and the tree keys follow the direction.
  • Titles and headings use dir="auto". Code blocks stay left-to-right.
  • Previous and next chevrons mirror.

Styling & tokens

Uses the surface, border, selected and focus tokens. Target data-slot of the parts. The sidebar shows from the lg breakpoint and the rail from xl.

Do / Don't

  • Do give every page a stable id and keep the tree shallow (two or three levels).
  • Do write ## and ### headings so the rail has something to show.
  • Don't nest the shell inside another sticky header. It manages its own.

Lab

https://docs.nasaqui.com/?path=/docs/components-layout-docs-shell--docs

On this page