Relevant source files
The following files were used as context for generating this wiki page:
- packages/radix-ui/src/layouts/notebook/page/index.tsx
- packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx
- packages/base-ui/src/layouts/notebook/slots/sidebar.tsx
- packages/base-ui/src/layouts/notebook/page/index.tsx
- packages/base-ui/src/layouts/flux/page/index.tsx
- packages/radix-ui/src/layouts/flux/index.tsx
- packages/base-ui/src/layouts/flux/slots/sidebar.tsx
- packages/base-ui/src/layouts/flux/index.tsx
- packages/radix-ui/src/layouts/flux/page/index.tsx
- packages/radix-ui/src/layouts/flux/slots/sidebar.tsx
- packages/openapi/src/ui/operation/index.tsx
- packages/radix-ui/src/layouts/notebook/client.tsx
- packages/radix-ui/src/layouts/notebook/slots/header.tsx
- packages/base-ui/src/layouts/notebook/slots/header.tsx
- packages/base-ui/src/layouts/notebook/client.tsx
- packages/base-ui/src/layouts/flux/page/slots/container.tsx
- packages/base-ui/src/layouts/home/index.tsx
- packages/base-ui/src/layouts/flux/page/slots/footer.tsx
- packages/base-ui/src/components/sidebar/base.tsx
- packages/radix-ui/src/layouts/notebook/slots/container.tsx
- packages/radix-ui/src/layouts/home/index.tsx
- packages/radix-ui/src/components/sidebar/base.tsx
- packages/radix-ui/src/layouts/notebook/index.tsx
- packages/base-ui/src/layouts/notebook/slots/container.tsx
- packages/preview/src/pages/...slugs.tsx
- packages/base-ui/src/layouts/notebook/index.tsx
- packages/base-ui/src/layouts/notebook/page/slots/container.tsx
- packages/radix-ui/src/layouts/notebook/page/slots/container.tsx
- packages/radix-ui/src/layouts/flux/page/slots/container.tsx
- packages/cli/src/commands/customise.ts
Overview
Fumadocs provides a modular layout architecture built around customizable slot components, context providers, and responsive structural containers across package implementations like Radix UI Primitives and Base UI Components. This architecture decomposes complex documentation interfaces—such as the notebook and Ui Layouts Layout variants—into discrete, swappable parts including sidebars, headers, navigation panels, and page containers. By managing state centrally through React context hooks and supporting flexible slot overrides via the Fumadocs CLI, developers can easily customize individual layout pieces or construct tailored documentation experiences from the ground up without modifying core logic.
Sources: packages/radix-ui/src/layouts/notebook/page/index.tsx:77-124, packages/radix-ui/src/layouts/flux/index.tsx:33-42, packages/cli/src/commands/customise.ts:116-160
Layout Composition and Slot Contract
Slot Interfaces and Component Bindings
Layout composition across UI implementations relies on typed slot contracts that permit developers to override structural sub-components without breaking internal context bindings. In the Flux layout, the DocsSlots interface extends BaseSlots by defining custom component types for container, tabDropdown, and a structured sidebar object containing provider, trigger, root, and useSidebar hooks.
export interface DocsSlots extends BaseSlots {
container: FC<ComponentProps<'div'>>;
tabDropdown: FC<TabDropdownProps>;
sidebar: {
provider: FC<SidebarProviderProps>;
trigger: FC<ComponentProps<'button'>>;
root: FC<SidebarProps>;
useSidebar: () => { collapsed: boolean; open: boolean; setOpen: (v: boolean) => void };
};
}Sources: packages/radix-ui/src/layouts/flux/index.tsx:33-42
Similarly, the Home layout defines HomeSlots extending BaseSlots with header and container slots, bound directly to React context via createContext and consumed through custom hooks like useHomeLayout.
export interface HomeSlots extends BaseSlots {
header: FC<ComponentProps<'header'>>;
container: FC<ComponentProps<'main'>>;
}Sources: packages/radix-ui/src/layouts/home/index.tsx:22-25
| Layout Variant | Primary Container Slot | Header / Navigation Slot | Sidebar Slot Structure |
|---|---|---|---|
| Flux Layout | slots.container (Container) | renderNavigationPanel / tabDropdown | slots.sidebar (provider, root, trigger, useSidebar) |
| Home Layout | slots.container (Container) | slots.header (Header) | Not applicable (nav enabled via nav.enabled) |
| Notebook Layout | Resolved via LayoutBody / containerProps | tabMode (sidebar or navbar) | Sidebar options and tab resolution |
Sources: packages/radix-ui/src/layouts/flux/index.tsx:33-41, packages/radix-ui/src/layouts/home/index.tsx:22-25, packages/radix-ui/src/layouts/notebook/index.tsx:13-21
Note
Layout contexts enforce strict tree containment; attempting to consume layout hooks outside their respective provider wrappers throws an explicit runtime error instructing developers to verify component nesting. Sources: packages/radix-ui/src/layouts/flux/index.tsx:69-76, packages/radix-ui/src/layouts/home/index.tsx:34-39
Tab Resolution and Layout Options
The Notebook and Flux layouts accept flexible tab configurations through the tabs property, which can be provided as an explicit array of LayoutTab objects, a GetLayoutTabsOptions configuration object, or set to false to disable tabs entirely. When passed as an object or omitted entirely, getLayoutTabs dynamically extracts tab structures from the page tree.
const resolvedTabs = useMemo(() => {
if (Array.isArray(tabs)) {
return tabs;
}
if (typeof tabs === 'object') {
return getLayoutTabs(tree, tabs);
}
if (tabs !== false) {
return getLayoutTabs(tree);
}
return [];
}, [tabs, tree]);Sources: packages/radix-ui/src/layouts/notebook/index.tsx:38-49
Warning
Defining tabs inside sidebar options is deprecated across notebook and flux layouts; configure layout-level tabs directly on the root layout props to prevent fallback warnings.
Sources: packages/radix-ui/src/layouts/flux/index.tsx:56-59, packages/radix-ui/src/layouts/notebook/index.tsx:23-28
Sidebar Primitive Architecture
Provider Contexts and Mode Detection
The sidebar architecture relies on provider contexts and reactive state hooks to manage visibility across viewports. SidebarProvider initializes state variables for open and collapsed, while determining the active display mode via media query evaluation.
type Mode = 'drawer' | 'full';
interface SidebarContext {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
collapsed: boolean;
setCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
closeOnRedirect: RefObject<boolean>;
defaultOpenLevel: number;
prefetch?: boolean;
mode: Mode;
}Sources: packages/base-ui/src/components/sidebar/base.tsx:32-64, packages/radix-ui/src/components/sidebar/base.tsx:32-64
The active layout mode switches between 'drawer' and 'full' based on a screen width breakpoint of 768px.
| Mode Value | Viewport Condition | Behavior |
|---|---|---|
drawer | (width < 768px) | Mobile overlay drawer controlled by SidebarDrawerOverlay and SidebarDrawerContent |
full | (width >= 768px) | Persistent desktop sidebar supporting collapsing and hover peek states |
Sources: packages/base-ui/src/components/sidebar/base.tsx:84-84, packages/radix-ui/src/components/sidebar/base.tsx:84-84
Important
Consuming useSidebar outside of a component wrapped in SidebarProvider throws an explicit runtime error requiring a parent layout context such as <DocsLayout />.
Sources: packages/base-ui/src/components/sidebar/base.tsx:114-122, packages/radix-ui/src/components/sidebar/base.tsx:114-122
Viewport and Scroll Management
Sidebar content is rendered within SidebarViewport, which nests a ScrollArea and ScrollViewport component combined with overflow containment and vertical fade gradient mask utility classes.
export function SidebarViewport({ className, ...props }: ScrollAreaProps) {
return (
<ScrollArea
className={(s) =>
cn('min-h-0 flex-1', typeof className === 'function' ? className(s) : className)
}
{...props}
>
<ScrollViewport className="p-4 overscroll-contain mask-[linear-gradient(to_bottom,transparent,white_12px,white_calc(100%-12px),transparent)]">
{props.children}
</ScrollViewport>
</ScrollArea>
);
}Sources: packages/base-ui/src/components/sidebar/base.tsx:222-235
Tip
SidebarContent monitors pointer entry and leave events to handle collapsible sidebar peek behavior on desktop, applying a 500ms close delay when the mouse pointer leaves towards the page body rather than the viewport edge.
Sources: packages/base-ui/src/components/sidebar/base.tsx:170-180, packages/radix-ui/src/components/sidebar/base.tsx:172-182
Navigation Sidebar Slot Wiring
Notebook Layout Sidebar Assembly
The notebook layout sidebar implementation structures its contents using base primitives, link item renderers, and page tree configurations. SidebarProps accepts optional page tree components, banner elements, footer nodes, and a desktop collapsing flag.
Sources: packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx:50-60
export interface SidebarProps extends ComponentProps<'aside'> {
components?: Partial<SidebarPageTreeComponents>;
banner?: ReactNode | FC<ComponentProps<'div'>>;
footer?: ReactNode | FC<ComponentProps<'div'>>;
/**
* Support collapsing the sidebar on desktop mode
*
* @defaultValue true
*/
collapsible?: boolean;
}Sources: packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx:50-60
The SidebarContent component checks useNotebookLayout() to determine navigation mode settings, evaluating whether navMode is set to 'auto' or alternative layout parameters to calculate sticky positioning offsets against top row heights.
Sources: packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx:78-84
Warning
SidebarContent inside the notebook layout applies fixed positioning calculations that depend directly on --fd-docs-row-1 or --fd-docs-row-2 CSS variables; altering top row heights without matching these variable definitions breaks vertical alignment.
Sources: packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx:92-95
Mobile drawer toggles are governed by SidebarDrawer, which pairs Base.SidebarDrawerOverlay featuring backdrop blur effects with Base.SidebarDrawerContent styled as a fixed right- or left-hand panel constrained to 85% width up to a maximum of 380px.
Sources: packages/radix-ui/src/layouts/notebook/slots/sidebar.tsx:128-146
Flux Layout Sidebar Integration
The Flux layout implements its sidebar slot by pulling menu items from useFluxLayout(), filtering out icon-only entries, and passing remaining items through SidebarLinkItem alongside page tree renderers and viewport wrappers.
Sources: packages/radix-ui/src/layouts/flux/slots/sidebar.tsx:64-81
export function Sidebar({ footer, banner, components, ...rest }: SidebarProps) {
const { menuItems } = useFluxLayout();
return (
<SidebarContent {...rest}>
<div className="flex flex-col gap-3 p-4 pb-2 empty:hidden">{banner}</div>
<Base.SidebarViewport viewport={{ className: '*:gap-0!' }}>
{menuItems
.filter((v) => v.type !== 'icon')
.map((item, i, list) => (
<SidebarLinkItem key={i} item={item} className={cn(i === list.length - 1 && 'mb-4')} />
))}
<SidebarPageTree {...components} />
</Base.SidebarViewport>
{footer}
</SidebarContent>
);
}Sources: packages/radix-ui/src/layouts/flux/slots/sidebar.tsx:64-81
Animated sidebar triggers switch between open and closed state icons using motion transitions: Sources: packages/radix-ui/src/layouts/flux/slots/sidebar.tsx:83-109
| State | Icon Component | Animation Transition |
|---|---|---|
| Open | XIcon | y: '100%' initial, duration: 0.2 |
| Closed | SidebarIcon | y: '100%' initial, duration: 0.2 |
Sources: packages/radix-ui/src/layouts/flux/slots/sidebar.tsx:83-109
Tip
The Flux sidebar modal registers a global keydown event listener via useEffectEvent that intercepts the Escape key when open is true, immediately dismissing the drawer and preventing default event propagation.
Sources: packages/radix-ui/src/layouts/flux/slots/sidebar.tsx:116-127
Header and Navbar Interactions
Header Orchestration and Layout Tabs
The Header component implements sticky header navigation under the grid area #nd-subnav, incorporating backdrop blur styling and dynamic background transparency based on isNavTransparent and sidebar open states. Navigation modes are evaluated through nav.mode, defaulting to 'auto', while sidebar collapse triggers adapt their visibility depending on whether collapse rules and top navigation modes are active.
<header
id="nd-subnav"
data-transparent={isNavTransparent && !open}
{...props}
className={cn(
'sticky [grid-area:header] flex flex-col top-(--fd-docs-row-1) z-10 backdrop-blur-sm transition-colors data-[transparent=false]:bg-fd-background/80 layout:[--fd-header-height:--spacing(14)]',
showLayoutTabs && 'lg:layout:[--fd-header-height:--spacing(24)]',
props.className,
)}
>Sources: packages/radix-ui/src/layouts/notebook/slots/header.tsx:36-45
Layout tabs are rendered via LayoutHeaderTabs when tabMode is set to 'navbar' and tabs.length exceeds zero. Active tabs are resolved against the current route pathname using isLayoutTabActive, applying primary border styling to matching route entries while supporting unlisted route filtering.
| Tab Property | Type / Behavior | Purpose |
|---|---|---|
title | ReactNode | Display label inside the navbar tab anchor |
url | string | Target destination route for the layout tab |
unlisted | boolean | Hides the tab when inactive if set to true |
props | ComponentProps<'a'> | Additional custom attributes or className overrides |
Sources: packages/radix-ui/src/layouts/notebook/slots/header.tsx:161-196
Warning
When tabMode is configured as 'navbar', the header height definition automatically scales from --spacing(14) up to --spacing(24) on large viewports (lg:layout:[--fd-header-height:--spacing(24)]), which directly impacts sticky offset calculations for content containers and sidebars.
Sources: packages/radix-ui/src/layouts/notebook/slots/header.tsx:40-42
Navbar Link Items and Device Pointer Controls
Navbar link items are processed by filtering out icon-only entries for desktop display while mapping icon items to ghost button variants with muted text styling. Custom items bypass standard rendering to execute their embedded children directly, whereas menu items delegate to popover dropdown subcomponents.
<div className="flex flex-1 items-center justify-end md:gap-2">
<div className="flex items-center gap-6 empty:hidden max-lg:hidden">
{navItems
.filter((item) => item.type !== 'icon')
.map((item, i) => (
<NavbarLinkItem key={i} item={item} />
))}
</div>
{navItems
.filter((item) => item.type === 'icon')
.map((item, i) => (
<LinkItem
key={i}
item={item}
className={cn(
buttonVariants({ size: 'icon-sm', color: 'ghost' }),
'text-fd-muted-foreground max-lg:hidden',
)}
aria-label={item.label}
>
{item.icon}
</LinkItem>
))}Sources: packages/radix-ui/src/layouts/notebook/slots/header.tsx:86-108
Mobile layouts hide desktop link items and search triggers in favor of compact search buttons (slots.searchTrigger.sm) and sidebar drawer toggles (slots.sidebar.trigger). On desktop viewports, language selection components (slots.languageSelect.root), theme switches (slots.themeSwitch), and collapsible sidebar triggers are aligned within an end-oriented flex container.
Tip
Custom navigation components can completely bypass the default header layout by passing a custom React element directly to nav.component, triggering an immediate return statement before standard CSS classes or DOM nodes are evaluated.
Sources: packages/radix-ui/src/layouts/notebook/slots/header.tsx:33-33
Page Content and Container Structures
Notebook Layout Container and Grid Template
The Container component within the notebook layout establishes a CSS grid container (#nd-notebook-layout) that manages column and row dimensions for sidebars, headers, table of contents popovers, and main article areas. It tracks sidebar collapse state via slots.sidebar?.useSidebar?.() and applies a temporary transition attribute data-column-changed={isCollapseChanged} whenever collapse toggles occur.
export function Container(props: ComponentProps<'div'>) {
const {
props: { nav },
slots,
} = useNotebookLayout();
const pageCol =
'calc(var(--fd-layout-width,97rem) - var(--fd-sidebar-col) - var(--fd-toc-width))';
const { collapsed } = slots.sidebar?.useSidebar?.() ?? {};
const [previousCollapsed, setPreviousCollapsed] = useState(collapsed);
const isCollapseChanged = previousCollapsed !== collapsed;
// will only set data attribute for an instant
useEffect(() => {
if (isCollapseChanged) setPreviousCollapsed(collapsed);
}, [collapsed, isCollapseChanged]);
Sources: packages/radix-ui/src/layouts/notebook/slots/container.tsx:7-22, packages/base-ui/src/layouts/notebook/slots/container.tsx:7-22
The grid layout geometry calculates column widths using var(--fd-layout-width, 97rem), subtracting sidebar and TOC widths to resolve the central page column (pageCol). The template columns string enforces minimum content sizing alongside explicit widths for sidebars (var(--fd-sidebar-col)), article bounds, and table of contents columns (var(--fd-toc-width)). Depending on nav.mode, header positions swap between top grid areas and standard placement.
| Grid Variable | Source Formula / Value | Purpose |
|---|---|---|
pageCol | calc(var(--fd-layout-width,97rem) - var(--fd-sidebar-col) - var(--fd-toc-width)) | Dynamic column span for the central document area |
--fd-sidebar-col | collapsed ? '0px' : 'var(--fd-sidebar-width)' | Active width allocated to the navigation sidebar column |
--fd-docs-row-1 | var(--fd-banner-height, 0px) | Top vertical offset tracking optional banner height |
--fd-docs-row-2 | calc(var(--fd-docs-row-1) + var(--fd-header-height)) | Vertical row boundary tracking sticky header placement |
--fd-docs-row-3 | calc(var(--fd-docs-row-2) + var(--fd-toc-popover-height)) | Vertical boundary for TOC mobile popover regions |
Sources: packages/radix-ui/src/layouts/notebook/slots/container.tsx:12-42, packages/base-ui/src/layouts/notebook/slots/container.tsx:12-42
Warning
The container enables grid template transitions only when data-column-changed is active (data-[column-changed=true]:transition-[grid-template-columns]). Direct mutation of collapsed without updating previousCollapsed via the layout effect will skip the column width animation frame.
Sources: packages/radix-ui/src/layouts/notebook/slots/container.tsx:19-47, packages/base-ui/src/layouts/notebook/slots/container.tsx:19-47
Article Wrapper and Slot Integration
The page container slot (<article id="nd-page">) structures individual documentation pages across notebook and flux layouts, binding [grid-area:main] to position itself correctly inside the CSS grid. It reads full boolean state from useDocsPage() to govern maximum width constraints, applying standard typography padding and responsive gaps.
export function Container(props: ComponentProps<'article'>) {
const { full } = useDocsPage();
return (
<article
id="nd-page"
data-full={full}
{...props}
className={cn(
'flex flex-col [grid-area:main] px-4 py-6 gap-4 md:px-6 md:pt-8 xl:px-8 xl:pt-14 *:max-w-[900px]',
full && '*:max-w-[1285px]',
props.className,
)}Sources: packages/radix-ui/src/layouts/notebook/page/slots/container.tsx:7-19, packages/base-ui/src/layouts/notebook/page/slots/container.tsx:6-18, packages/radix-ui/src/layouts/flux/page/slots/container.tsx:7-19
Within DocsPage, optional slots such as breadcrumbs and footers are conditionally mounted alongside children inside the article container. Table of contents providers wrap the entire page structure, managing TOC item propagation based on tocEnabled and tocPopoverEnabled flags.
| Layout Variant | Container Max-Width (Standard) | Container Max-Width (full: true) | Grid Area Binding |
|---|---|---|---|
Notebook (radix-ui / base-ui) | *:max-w-[900px] | *:max-w-[1285px] | [grid-area:main] |
Flux (radix-ui) | max-w-[900px] | max-w-[1200px] | [grid-area:main] |
Sources: packages/radix-ui/src/layouts/notebook/page/slots/container.tsx:15-18, packages/base-ui/src/layouts/notebook/page/slots/container.tsx:15-16, packages/radix-ui/src/layouts/flux/page/slots/container.tsx:16-17
Tip
Setting full={true} on DocsPage overrides standard child constraints, expanding maximum child widths up to 1285px in notebook layouts and 1200px in flux layouts while automatically disabling default desktop TOC rendering unless explicitly re-enabled.
Sources: packages/radix-ui/src/layouts/notebook/page/index.tsx:103-104, packages/radix-ui/src/layouts/notebook/page/slots/container.tsx:15-18, packages/radix-ui/src/layouts/flux/page/slots/container.tsx:16-17
Article Footers and Pagination Controls
Footer Item Resolution and Active Route Matching
The pagination footer system is driven by the Footer component, which accepts explicit pagination items or automatically discovers them using useFooterItems() and the current router pathname. When explicit items are not passed as props, the component finds the active index within the footer list by matching items against the current pathname using isActive(item.url, pathname).
export function Footer({ items, children, className, ...props }: FooterProps) {
const footerList = useFooterItems();
const pathname = usePathname();
const { previous, next } = useMemo(() => {
if (items) return items;
const idx = footerList.findIndex((item) => isActive(item.url, pathname));
if (idx === -1) return {};
return {
previous: footerList[idx - 1],
next: footerList[idx + 1],
};
}, [footerList, items, pathname]);Sources: packages/base-ui/src/layouts/flux/page/slots/footer.tsx:25-38
If no matching route is found (idx === -1), pagination objects are omitted entirely. Otherwise, previous maps to footerList[idx - 1] and next maps to footerList[idx + 1].
Warning
If isActive fails to resolve the current pathname against any entry in footerList, both previous and next pagination slots remain undefined, rendering an empty container grid unless explicit items are provided via props.
Sources: packages/base-ui/src/layouts/flux/page/slots/footer.tsx:28-38
Container Layout and Pagination Item Rendering
The footer wrapper applies a container query (@container) and dynamically toggles CSS grid columns based on the presence of adjacent navigation links. When both previous and next items exist, it assigns grid-cols-2; otherwise, it falls back to a single column layout (grid-cols-1).
return (
<>
<div
className={cn(
'@container grid gap-4',
previous && next ? 'grid-cols-2' : 'grid-cols-1',
className,
)}
{...props}
>
{previous && <FooterItem item={previous} index={0} />}
{next && <FooterItem item={next} index={1} />}
</div>
{children}
</>
);
}Sources: packages/base-ui/src/layouts/flux/page/slots/footer.tsx:40-56
Individual footer links are rendered through the FooterItem subcomponent, which uses useTranslations({ note: 'pagination' }) to supply localized fallback text (Previous Page or Next Page) when an item lacks an explicit description. The icon and text layout adapt based on the index: index 0 (previous) renders a left chevron (ChevronLeft) with standard alignment, while index 1 (next) reverses the flex-row direction, right-aligns the text, and renders a right chevron (ChevronRight).
| Index | Direction | Icon | Default Description Fallback | CSS Alignment Classes |
|---|---|---|---|---|
0 (Previous) | Standard | ChevronLeft | t('Previous Page') | flex flex-col gap-2 |
1 (Next) | Reversed (flex-row-reverse) | ChevronRight | t('Next Page') | flex flex-col gap-2 text-end |
Sources: packages/base-ui/src/layouts/flux/page/slots/footer.tsx:58-83
Related
On This Page
OverviewLayout Composition and Slot ContractSlot Interfaces and Component BindingsTab Resolution and Layout OptionsSidebar Primitive ArchitectureProvider Contexts and Mode DetectionViewport and Scroll ManagementNavigation Sidebar Slot WiringNotebook Layout Sidebar AssemblyFlux Layout Sidebar IntegrationHeader and Navbar InteractionsHeader Orchestration and Layout TabsNavbar Link Items and Device Pointer ControlsPage Content and Container StructuresNotebook Layout Container and Grid TemplateArticle Wrapper and Slot IntegrationArticle Footers and Pagination ControlsFooter Item Resolution and Active Route MatchingContainer Layout and Pagination Item RenderingRelated