# Docs Layout

Build documentation sites with sidebar navigation, table of contents, and surround links.

## When to use

- Technical documentation sites
- Knowledge bases, help centers
- Any content-heavy site with hierarchical navigation

> Requires `@nuxt/content` — see [conventions](../guidelines/conventions.md#content-module-integration) for setup (module order + `@source`).

## Component tree

```
UApp
├── UHeader
├── UMain
│   └── NuxtLayout (docs)
│       └── UPage
│           ├── #left → UPageAside → UContentNavigation
│           └── NuxtPage
│               ├── UPageHeader
│               ├── UPageBody → ContentRenderer + UContentSurround
│               └── #right → UContentToc
└── UFooter
```

## App shell

```vue [app.vue]
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui'

const route = useRoute()

const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))

provide('navigation', navigation)

const items = computed<NavigationMenuItem[]>(() => [{
  label: 'Docs',
  to: '/docs/getting-started',
  active: route.path.startsWith('/docs')
}])
</script>

<template>
  <UApp>
    <UHeader>
      <template #title>
        <Logo class="h-6 w-auto" />
      </template>

      <UNavigationMenu :items="items" />

      <template #right>
        <UContentSearchButton />
        <UColorModeButton />
      </template>
    </UHeader>

    <UMain>
      <NuxtLayout>
        <NuxtPage />
      </NuxtLayout>
    </UMain>

    <UFooter />

    <UContentSearch :navigation="navigation" />
  </UApp>
</template>
```

## Layout

```vue [layouts/docs.vue]
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'

const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>

<template>
  <UPage>
    <template #left>
      <UPageAside>
        <UContentNavigation :navigation="navigation" />
      </UPageAside>
    </template>

    <slot />
  </UPage>
</template>
```

## Page

```vue [pages/docs/[...slug].vue]
<script setup lang="ts">
const route = useRoute()

definePageMeta({ layout: 'docs' })

const { data: page } = await useAsyncData(route.path, () => {
  return queryCollection('docs').path(route.path).first()
})

const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
  return queryCollectionItemSurroundings('docs', route.path)
})
</script>

<template>
  <UPage>
    <UPageHeader :title="page.title" :description="page.description" />

    <UPageBody>
      <ContentRenderer :value="page" />

      <USeparator />

      <UContentSurround :surround="surround" />
    </UPageBody>

    <template #right>
      <UContentToc :links="page.body.toc.links" />
    </template>
  </UPage>
</template>
```

### How nesting works

The outer `UPage` in the layout handles the **left sidebar**. The inner `UPage` in the page handles the **right sidebar**. They nest correctly — this is intentional.

### Common mistakes

- Not providing navigation via `provide`/`inject` — the layout needs it from the app shell.
- Forgetting `UContentSearch` in app.vue — search won't work without it.
- Using `UContentSearchButton` without `UContentSearch` — the button opens search, but the search component must exist.

## Key components

- `UPage` — multi-column grid with `#left`, `#default`, `#right` slots
- `UPageAside` — sticky sidebar wrapper (visible from `lg` breakpoint)
- `UContentNavigation` — sidebar navigation tree from Nuxt Content
- `UContentToc` — table of contents from page headings
- `UContentSurround` — prev/next links
- `UContentSearch` / `UContentSearchButton` — search command palette
- `UPageAnchors` — simpler alternative to full TOC
