Split Sidebar into Tabs
3 min read
This recipe provides a comprehensive walkthrough for implementing a tabbed sidebar switcher in Starlight, similar to the one used in the official Astro Documentation.
By default, Starlight provides a vertical sidebar. For larger documentation sites, a tabbed interface can help organize content into top-level categories, making navigation more intuitive.
Step-by-Step Guide
Section titled “Step-by-Step Guide”-
Centralize Navigation Labels
Create a file to manage your navigation labels. This helps with localization and centralized management of tab titles.
src/content/nav/en.ts export default {"astro.recipes": "Astro Recipes","starlight.recipes": "Starlight Recipes",} as const; -
Modularize Sidebar Configuration
Create a dedicated configuration file for your sidebar. This file will export separate arrays for each tab, combined into a single configuration for Starlight. Example:
config/sidebar.ts import type { StarlightUserConfig } from "@astrojs/starlight/types";import enLabels from "../src/content/nav/en";type NavKey = keyof typeof enLabels;/*** Helper to create a sidebar group with localized labels.*/export function group(key: NavKey, group: any): any {return {label: enLabels[key],...group,};}export const astroSidebar = [group("astro.recipes", {autogenerate: { directory: "astro" },}),] satisfies StarlightUserConfig["sidebar"];export const starlightSidebar = [group("starlight.recipes", {autogenerate: { directory: "starlight" },}),] satisfies StarlightUserConfig["sidebar"]; -
Implement Tab Components
Create the following components in
src/components/tabs/to handle the UI and logic of the switcher.src/components/tabs/TabbedContent.astro ---export interface Props {class?: string;}---<tabbed-content class={Astro.props.class}><ul class="tab-list"><slot name="tab-list" /></ul><div class="panels"><slot /></div></tabbed-content><script>class Tabs extends HTMLElement {// ... (Refer to full source in the custom component step)}customElements.define("tabbed-content", Tabs);</script>src/components/tabs/TabListItem.astro ---import type { HTMLAttributes } from 'astro/types';export interface Props {id: string;initial?: boolean;class?: string;}const { id, initial } = Astro.props;const linkAttributes: HTMLAttributes<'a'> = initial ? { 'data-initial': 'true' } : {};---<li class:list={Astro.props.class}><a href={'#' + id} {...linkAttributes}><slot /></a></li>src/components/tabs/TabPanel.astro ---export interface Props {id: string;initial?: boolean;}const { id, initial } = Astro.props;---<div id={id} data-initial={initial ? "true" : undefined}><slot /></div> -
Create the Custom Sidebar Component
This component overrides Starlight’s default Sidebar. It uses the components from the previous step and assigns icons like “astro” and “starlight”.
src/components/Sidebar.astro ---import { Icon } from "@astrojs/starlight/components";import SidebarPersister from "@astrojs/starlight/components/SidebarPersister.astro";import SidebarSublist from "@astrojs/starlight/components/SidebarSublist.astro";import TabbedContent from "./tabs/TabbedContent.astro";import TabListItem from "./tabs/TabListItem.astro";import TabPanel from "./tabs/TabPanel.astro";const { sidebar } = Astro.locals.starlightRoute;// ... logic for active tab selection and icons ...---<SidebarPersister><TabbedContent class="tabbed-sidebar"><Fragment slot="tab-list">{sidebar.map((group: any, index: number) => (<TabListItemid={makeId(group.label)}initial={anyTabIsCurrent ? isCurrent(group.entries) : index === 0}class="tab-item"><Icon name={getIcon(group.label)} /> {group.label}</TabListItem>))}</Fragment>{sidebar.map((group: any, index: number) => (<TabPanel id={makeId(group.label)} initial={isInitial}><SidebarSublist sublist={group.entries} /></TabPanel>))}</TabbedContent></SidebarPersister> -
Configure Astro
Finally, register your custom component and import the modular sidebar configs.
astro.config.mjs import starlight from "@astrojs/starlight";import { defineConfig } from "astro/config";import { astroSidebar, starlightSidebar } from "./config/sidebar";export default defineConfig({integrations: [starlight({components: {Sidebar: "./src/components/Sidebar.astro",},sidebar: [...astroSidebar, ...starlightSidebar],}),],});