Sidebar
The sidebar is auto-generated from your file structure. Customize its appearance through config.
Custom Title
The sidebar title supports strings or React components:
nav: {
title: (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Rocket size={14} />
<span>My Docs</span>
</div>
),
url: "/docs",
},Icons
Add icons to sidebar items via frontmatter:
---
title: "Getting Started"
icon: "rocket"
---Register icons in your config:
icons: {
rocket: <Rocket size={16} />,
book: <BookOpen size={16} />,
},Collapsible Groups
Nested directories automatically become collapsible groups. A directory with its own page.mdx becomes a folder with an index page:
app/docs/
plugins/
page.mdx # Folder index (clickable)
organizations/
page.mdx # Child page
two-factor/
page.mdx # Child pageFlat Mode
Render all sidebar items without collapsible sections (Mintlify-style):
sidebar: { flat: true },Sidebar Header & Footer
Add custom content above and below the navigation items using banner and footer:
sidebar: {
banner: (
<div style={{
padding: "12px 16px",
borderBottom: "1px solid var(--color-fd-border)",
fontSize: "13px",
}}>
<strong>v2.0 is here</strong>
<p style={{ margin: 0 }}>Check out the new features.</p>
</div>
),
footer: (
<div style={{
padding: "12px 16px",
borderTop: "1px solid var(--color-fd-border)",
fontSize: "12px",
color: "var(--color-fd-muted-foreground)",
}}>
Built with @farming-labs/docs
</div>
),
},The banner renders above the navigation items and footer renders below them, both inheriting the theme's sidebar styling automatically.
Next.js and TanStack Start can pass React nodes directly in docs.config.tsx. For non-React frameworks, use named slots:
Pass sidebarHeader and sidebarFooter snippets:
<DocsLayout tree={data.tree} config={data.config}>
{#snippet sidebarHeader()}
<div class="sidebar-promo">v2.0 is here</div>
{/snippet}
{#snippet sidebarFooter()}
<div class="sidebar-credits">Built with docs</div>
{/snippet}
{@render children()}
</DocsLayout>Use sidebar-header and sidebar-footer named slots:
<DocsLayout tree={tree} config={config}>
<div slot="sidebar-header" class="sidebar-promo">
v2.0 is here
</div>
<div slot="sidebar-footer" class="sidebar-credits">
Built with docs
</div>
<slot />
</DocsLayout>Use #sidebar-header and #sidebar-footer slots:
<DocsLayout :tree="tree" :config="config">
<template #sidebar-header>
<div class="sidebar-promo">v2.0 is here</div>
</template>
<template #sidebar-footer>
<div class="sidebar-credits">Built with docs</div>
</template>
<slot />
</DocsLayout>Custom Sidebar Component
Replace the entire sidebar navigation with your own component. The component receives the full page tree with all parent-child relationships.
Pass a render function via sidebar.component in your config:
import type { SidebarComponentProps } from "@farming-labs/docs";
sidebar: {
component: ({ tree, collapsible, flat }: SidebarComponentProps) => (
<MySidebar tree={tree} />
),
},Your component receives the full tree structure:
"use client";
import { usePathname } from "next/navigation";
import type { SidebarTree, SidebarNode } from "@farming-labs/docs";
export function MySidebar({ tree }: { tree: SidebarTree }) {
const pathname = usePathname();
function renderNode(node: SidebarNode) {
if (node.type === "page") {
return (
<a
href={node.url}
className={pathname === node.url ? "active" : ""}
>
{node.name}
</a>
);
}
// node.type === "folder"
return (
<details open>
<summary>{node.name}</summary>
<div>
{node.index && (
<a href={node.index.url}>{node.index.name}</a>
)}
{node.children.map((child) => renderNode(child))}
</div>
</details>
);
}
return (
<nav>
{tree.children.map((node) => renderNode(node))}
</nav>
);
}Pass a render function via sidebar.component in your config:
import type { SidebarComponentProps } from "@farming-labs/docs";
sidebar: {
component: ({ tree, collapsible, flat }: SidebarComponentProps) => (
<MySidebar tree={tree} />
),
},Your component can read the current route with TanStack Router:
import { useRouterState } from "@tanstack/react-router";
import type { SidebarTree, SidebarNode } from "@farming-labs/docs";
export function MySidebar({ tree }: { tree: SidebarTree }) {
const pathname = useRouterState({
select: (state) => state.location.pathname,
});
function renderNode(node: SidebarNode) {
if (node.type === "page") {
return (
<a
href={node.url}
className={pathname === node.url ? "active" : ""}
>
{node.name}
</a>
);
}
return (
<details open>
<summary>{node.name}</summary>
<div>
{node.index && <a href={node.index.url}>{node.index.name}</a>}
{node.children.map((child) => renderNode(child))}
</div>
</details>
);
}
return <nav>{tree.children.map((node) => renderNode(node))}</nav>;
}Use the sidebar snippet on <DocsLayout>. It receives tree and isActive:
<script>
import DocsLayout from "@farming-labs/svelte-theme/DocsLayout.svelte";
let { data, children } = $props();
</script>
<DocsLayout tree={data.tree} config={data.config}>
{#snippet sidebar({ tree, isActive })}
<nav>
{#each tree.children as node}
{#if node.type === "page"}
<a
href={node.url}
class:active={isActive(node.url)}
>
{node.name}
</a>
{:else}
<details open>
<summary>{node.name}</summary>
{#each node.children as child}
{#if child.type === "page"}
<a href={child.url}>{child.name}</a>
{/if}
{/each}
</details>
{/if}
{/each}
</nav>
{/snippet}
{@render children()}
</DocsLayout>Use the sidebar named slot:
---
import DocsLayout from "@farming-labs/astro-theme/DocsLayout.astro";
import MySidebar from "../components/MySidebar.astro";
const { tree, config } = Astro.props;
---
<DocsLayout tree={tree} config={config}>
<MySidebar slot="sidebar" tree={tree} />
<slot />
</DocsLayout>Use the #sidebar scoped slot:
<template>
<DocsLayout :tree="tree" :config="config">
<template #sidebar="{ tree, isActive }">
<nav>
<template v-for="node in tree.children" :key="node.name">
<a
v-if="node.type === 'page'"
:href="node.url"
:class="{ active: isActive(node.url) }"
>
{{ node.name }}
</a>
<details v-else open>
<summary>{{ node.name }}</summary>
<a
v-for="child in node.children"
v-if="child.type === 'page'"
:key="child.url"
:href="child.url"
>
{{ child.name }}
</a>
</details>
</template>
</nav>
</template>
<slot />
</DocsLayout>
</template>Tree Structure
The tree prop has this shape:
interface SidebarTree {
name: string;
children: SidebarNode[];
}
type SidebarNode = SidebarPageNode | SidebarFolderNode;
interface SidebarPageNode {
type: "page";
name: string;
url: string;
icon?: unknown;
}
interface SidebarFolderNode {
type: "folder";
name: string;
icon?: unknown;
index?: SidebarPageNode; // folder's own landing page
children: SidebarNode[]; // recursive children
collapsible?: boolean;
defaultOpen?: boolean;
}All types are exported from @farming-labs/docs for full type safety.
Sidebar Style
theme: pixelBorder({
ui: {
sidebar: { style: "floating" },
},
}),Breadcrumbs
Enable breadcrumb navigation:
breadcrumb: { enabled: true },Shows Parent / Current Page with the parent being clickable.
How is this guide?