# Svelte DocSmith > A documentation framework for Svelte. Interactive examples in one real, stateful app, markdown as routes, and a sidebar that builds itself. # Introduction ## What is Svelte DocSmith? Svelte DocSmith is a framework for building documentation sites with Svelte 5 and SvelteKit. Your interactive examples live inside one real, stateful app. They are not sandboxed as isolated islands, and not screenshots of a component that used to work. You write markdown; DocSmith gives you the pipeline that turns it into styled, navigable, syntax-highlighted pages, and lets you drop live Svelte components straight into the prose. ## Why another docs tool? Most Svelte docs are written by hand or bolted onto a general-purpose static site generator. Either way you end up maintaining a navigation tree, wiring up a markdown pipeline, and rebuilding the same layout every project. DocSmith gives you the pipeline, the layout, and the navigation out of the box, so you can focus on the content. And because a page is a real SvelteKit route, an example in your docs is the same component your users import: running, stateful, and impossible to let rot. ## Highlights - **Markdown as routes.** `.md` files compile to real Svelte components via mdsvex. No loader, no catch-all route. - **Live examples.** Drop a component into a page; it runs, and its source is shown from the same file, so the two can never drift. - **Syntax highlighting.** Shiki runs at build time on the HAST tree, with a generous language set and dual light/dark themes. - **Nav derives itself.** The sidebar is built from each page's frontmatter, never hand-written. - **The whole chrome.** Header, collapsible sidebar, mobile nav, in-page table of contents, breadcrumbs, and prev/next links, all included. - **Yours to theme.** One CSS import ships the Tailwind and shadcn token system; override any token to make it your own. ## Where to next {#snippet icon()}{/snippet} Add DocSmith to a SvelteKit project and wire up the one-line CSS contract. {#snippet icon()}{/snippet} Register the pipeline and render your first page in four steps. # Installation ## Start a new project The fastest way to begin is the scaffolder. It creates a ready-to-run SvelteKit project already wired with DocSmith: the markdown pipeline, the Vite plugin, the style contract, a `DocsShell` layout, a 404 page, and a couple of sample pages. ```bash pnpm create svelte-docsmith my-docs ``` ```bash npm create svelte-docsmith@latest my-docs ``` ```bash yarn create svelte-docsmith my-docs ``` ```bash bun create svelte-docsmith my-docs ``` Then install dependencies and start the dev server: ```bash cd my-docs npm install npm run dev ``` That is the whole setup. Skip ahead to [Writing pages](/docs/writing-pages) to start authoring. The rest of this page covers adding DocSmith to a project you already have. ## Add to an existing project Svelte DocSmith is a SvelteKit library. Install it with your package manager of choice: ```bash pnpm add -D svelte-docsmith ``` ```bash npm install -D svelte-docsmith ``` ```bash yarn add -D svelte-docsmith ``` ```bash bun add -D svelte-docsmith ``` DocSmith expects **Svelte 5**, **SvelteKit 2**, and **Tailwind CSS v4** as peer dependencies, the same stack this documentation site runs on. A fresh `npx sv create` app with Tailwind selected already has them. ## The CSS contract Components are styled with Tailwind and shadcn design tokens. The whole contract is one import in your app's stylesheet: ```css @import 'tailwindcss'; @import 'svelte-docsmith/theme.css'; ``` `theme.css` makes Tailwind scan the package (so the utility classes its components use are generated), defines the shadcn theme tokens (`--background`, `--primary`, `--radius`, and the rest) for `:root` and `.dark`, and pulls in the typography and animation plugins. That single import is also the whole customization surface: redefine any token after it to rebrand the entire system. See [Theming](/docs/theming) for the full token list and how dark mode is wired. ## Next With the package installed, continue to the [Quick Start](/docs/quick-start) to wire up the pipeline and render your first page. # Quick Start Four steps take you from an installed package to a live docs page in the sidebar. Each one edits a single file. `npm create svelte-docsmith` does every step on this page for you. This walk through is for adding DocSmith to an existing app, or for understanding the pieces. See [Installation](/docs/installation) for the scaffolder. In `svelte.config.js`, add `.md` to your extensions and call `docsmith()`. It bundles mdsvex, Shiki highlighting with a generous language set, heading anchors, and the DocSmith page layout: ```js import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { docsmith } from 'svelte-docsmith/preprocess'; export default { extensions: ['.svelte', '.md'], preprocess: [vitePreprocess(), docsmith()], kit: { adapter: adapter() } }; ``` In `vite.config.ts`, add `docsmith()`. It scans your pages' frontmatter into the `svelte-docsmith/content` module and powers live examples: ```ts import { sveltekit } from '@sveltejs/kit/vite'; import tailwindcss from '@tailwindcss/vite'; import { docsmith } from 'svelte-docsmith/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [docsmith(), tailwindcss(), sveltekit()] }); ``` In `src/routes/docs/+layout.svelte`, render `DocsShell`. It builds the sidebar from the generated content index, so there is no nav array to maintain: ```svelte {@render children()} ``` Create `src/routes/docs/getting-started/+page.md`. The frontmatter drives the sidebar; everything below it is your content: ````md --- title: Getting Started description: Your first steps. section: Guides order: 1 --- ## Hello This is a real SvelteKit route. Code blocks are highlighted by Shiki, and you can emphasise a line with the notation transformer: ```ts const docs = loadDocs(); // [!code highlight] ``` ```` ## What you end up with Those four files sit exactly here: Drop a markdown file under `src/routes/docs/` and it appears in the sidebar, styled, highlighted, with breadcrumbs and a table of contents. To embed a running component, see [Writing pages](/docs/writing-pages), which covers frontmatter, live examples, and code highlighting in full. # Configuration Everything you wire up once: what the package exports, the config object you pass to the shell, and the two page-level components that own the site chrome. Props for the authoring components (`Callout`, `Tabs`, `Badge`, and the rest) live on their own pages in the [Components](/docs/components/callout) section. ## Package exports - **`svelte-docsmith`**: every component, plus `defineConfig`, `createSearchEngine`, `generateSitemap`, and the types. Components are documented in [Components](/docs/components/callout); the rest is below. - **`svelte-docsmith/preprocess`**: the mdsvex + Shiki preprocessor for `svelte.config.js`. - **`svelte-docsmith/vite`**: the Vite plugin (content index, search index, and the `?source` transform). - **`svelte-docsmith/content`**: the generated sidebar index, exported as `docs`. - **`svelte-docsmith/search`**: the generated full-text search index, exported as `docs` (lazy-load it; see [Search](/docs/search)). - **`svelte-docsmith/theme.css`**: the base style contract. - **`svelte-docsmith/themes/*.css`**: the pre-installed theme presets (see [Theming](/docs/theming)). ## defineConfig Validates a `DocsmithConfig` and returns it unchanged, throwing a clear error on an invalid or dynamically-built config instead of rendering a blank header. ```ts const config = defineConfig({ title: 'My Library', description: 'A short tagline, used as the default meta description.', url: 'https://my-library.dev', github: 'https://github.com/you/my-library', version: '1.0.0' }); ``` Site title, shown in the header/sidebar and as the <title> suffix. Default meta description, used for pages without their own. See SEO. Canonical site origin. Enables <link rel="canonical"> and absolute Open Graph URLs. Default social-share image (absolute, or a path resolved against url). Base URL for the per-page “Edit this page” link, e.g. https://github.com/you/repo/edit/main/apps/docs. Each page's source path is appended. (“Last updated” is added from git automatically.) GitHub URL; renders a link in the header when set. Version string shown in the header. Logo image src; falls back to the built-in book mark. Top-level header navigation links. A thin bar above the header. text is required; add a tag for a leading pill (e.g. "New") and an href to link it. It's dismissible by default and stays dismissed until you change id (or the text), so bump id to re-show a new announcement. Footer copyright line, titled link columns, and the “Powered by” toggle. This site runs one: the thin bar above the header is `config.announcement`. Its `id` tracks the library version, so it returns after each release and stays out of the way in between. Dismiss it and it holds until the next version. ## DocsShell The full documentation shell: header, sidebar, content area, and table of contents. ```svelte {@render children()} ``` The site config (see defineConfig above). The content index; the sidebar nav is derived from it. The rendered page. Enable the ⌘K search palette by lazily providing the generated index, e.g. {'() => import(\'svelte-docsmith/search\').then((m) => m.docs)'}. See Search. Override the head tags for this page (title, meta description). Doc pages get these from frontmatter automatically. See SEO. Custom logo mark for the header and mobile menu. Extra header controls, before the theme toggle. Content rendered below the page column. Render the decorative grid-and-glow page background. Show the "Copy page" split button on doc pages (copy as Markdown, view the raw .md, or open in ChatGPT / Claude). Needs the .md endpoint. See SEO. Show the estimated reading time on doc pages (computed at build time from the page's word count). Set false to hide it. void)"}> Show the "Was this page helpful?" widget at the foot of doc pages. Pass true for the UI alone, or a callback to record votes (wire it to your analytics). Omit to hide it. docs is the three-column shell; page is full-bleed content with the same header and footer but no sidebar or TOC. `ThemeProvider` and `ThemeToggle` handle light and dark with no consumer wiring. `DocsShell` mounts the provider internally, so you never touch `mode-watcher` yourself. Use `ThemeProvider` directly to wrap a page you build outside `DocsShell`. ## ErrorPage A styled 404 / error screen that keeps the site chrome (header, search, footer, theme). Drop it into a SvelteKit `+error.svelte`: ```svelte ``` Same config as DocsShell, so the chrome matches. Content index, so the header/footer nav still work. HTTP status. Defaults to the current page's status. Heading. Defaults to “Page not found” for 404, else “Something went wrong”. Body line. Defaults to the error message, then a status-appropriate default. Where the primary action links. Label of the primary action. Enable the ⌘K palette on the error page (same loader as DocsShell). ## Types - **`DocsmithConfig`**: the config object above. - **`DocsContentItem`**: a content-index entry with `title`, `path`, and optional `section`, `order`, `description`, `toc`. - **`SearchDoc`** / **`SearchResult`** / **`SearchEngine`**: the search index entry and the shape returned by [`createSearchEngine`](/docs/search). - **`CalloutVariant`** / **`BadgeVariant`**: the intent unions for `Callout` and `Badge`. The vendored shadcn primitives and internal helpers (the TOC engine, the clipboard utility, the markdown renderer map) are **not** part of the public API and may change between releases. # How it works ## Markdown as routes DocSmith leans on mdsvex, which compiles markdown into real Svelte components. A file becomes a page by its position on disk, with no loader and no catch-all route: The file above serves `/docs/guides/routing`. Because it is a normal SvelteKit route, you can drop interactive Svelte components straight into the markdown and they run as part of the same app. ## One name, two plugins DocSmith ships two things both called `docsmith()`, imported from two entry points. They do different jobs: - `svelte-docsmith/preprocess` is a **Svelte preprocessor**, added in `svelte.config.js`. It runs at compile time and turns each `.md` file into a styled, highlighted page (mdsvex, Shiki, heading anchors, the page layout). - `svelte-docsmith/vite` is a **Vite plugin**, added in `vite.config.ts`. It runs at build time and generates the content index (below) plus the `?source` transform that powers live examples. The preprocessor and the Vite plugin are not interchangeable. The preprocessor renders your pages; the Vite plugin builds the sidebar and live-example source. Register one without the other and either your pages or your navigation goes missing. ## Nav is derived, never written There is no navigation array to maintain. The `docsmith()` Vite plugin reads each page's frontmatter into the `svelte-docsmith/content` module, and `DocsShell` groups the entries into the sidebar: ```ts { title: 'How it works', description: 'The content model...', section: 'Core Concepts', // sidebar group order: 4 // sort key } ``` `section` names the group; `order` sorts entries within it; groups are ordered by the smallest `order` they contain. Add a page, and it slots into the sidebar in the right place automatically. ## Two tables of contents, two jobs DocSmith keeps two structures, and they never overlap: - The **content index** owns build-time structure. The `docsmith()` Vite plugin scans frontmatter into the sidebar navigation. - The runtime **TOC engine** owns in-page scroll tracking. It scans the rendered headings and highlights the section you are reading. ## The highlighting pipeline Code blocks are highlighted by [Shiki](https://shiki.style) inside the `docsmith()` preprocessor, with a generous default language set. Unknown languages fall back to plain text instead of failing the build: ```python def greet(name: str) -> str: return f"Hello, {name}" ``` Highlighting is dual-theme: the same markup carries light and dark colors and flips with the page theme, so your code reads correctly either way. # Writing pages ## A page is a file Every page is a `+page.md` file under `src/routes/docs/`. The directory name is the URL, so this file serves `/docs/guides/routing`: Create the file, and the page exists. ## Frontmatter The frontmatter block at the top of each page drives the sidebar. Four fields: | Field | Required | Purpose | | ------------- | -------- | -------------------------------------------------------------------------- | | `title` | yes | The sidebar label and page heading. | | `description` | no | One-line summary, shown under the title. | | `section` | no | Sidebar group, a string or a nested path. Omitted pages fall under "Docs". | | `order` | no | Sort key within the group. | ```md --- title: Routing description: How pages map to URLs. section: Guides order: 1 --- ``` `section` names the group and `order` sorts within it; groups themselves are ordered by the smallest `order` they contain. ### Nested sections Give `section` an array to nest a page inside a collapsible subsection. Each entry is one level of the group path: ```md --- title: Middleware section: [Guides, Advanced] order: 2 --- ``` This puts "Middleware" under a collapsible **Advanced** group inside **Guides**. Nesting can go as deep as you like, `order` still sorts each level, and a subsection inherits the smallest `order` of its pages. The branch holding the current page is expanded on load; the rest start collapsed. A page with no `title` is skipped by the sidebar. If a page isn't showing up, check its frontmatter before anything else: a stray indent or a typo'd key is the usual culprit. ## Headings Don't write an `#` (h1) in the body. The `title` from frontmatter is the page heading, so start your content at `##`. Every heading gets an anchor id automatically, and the in-page table of contents is built from `##` and `###` headings as the page renders. ## Code blocks Fenced code blocks are highlighted by Shiki at build time; tag the fence with a language. To emphasise a line, append the comment `// [!code highlight]` to it (a real comment in that language). Shiki strips the comment and highlights the line, like the second line below: ```ts const docs = loadDocs(); const current = docs.find((d) => d.active); // [!code highlight] ``` Unknown languages fall back to plain text rather than failing the build, so a stray ` ```mermaid ` won't break your site. Line highlighting is just the start. See [Code blocks](/docs/code-blocks) for diffs, focus, error and warning lines, and word highlighting. ## Live examples To show a real, running component next to its source, put the component in `src/lib/examples/`. Import `LiveExample`, then import your component twice: once as the component, and once with the `?source` query for its build-time highlighted source. Pass both to `LiveExample`: ```md ``` Both come from the same file, so the demo you render and the code you show can never drift. See [Live Examples](/docs/live-examples) for a running one. ## Tabbed content For alternatives such as package managers or framework variants, group blocks with `Tabs` and `TabItem`. Pass the tab labels as `items`; each `TabItem`'s `value` matches one label: ````md ```bash npm i -D svelte-docsmith ``` ```bash pnpm add -D svelte-docsmith ``` ```` See the [Components](/docs/components/callout) section for the full set you can drop into a page: callouts, steps, cards, accordions, file trees, badges, and more. # Code blocks Every fenced code block is highlighted by Shiki at build time. On top of that, you annotate lines and words with comment markers written right in the code. The marker comment is stripped from the rendered output, so what readers see stays clean. Each section below shows the markdown you write, then how it renders. ## Highlight a line Append `// [!code highlight]` to a line (a real comment in the fence's language) to give it a highlighted background. You write: ```text const config = defineConfig({ title: 'My Library' // [!code highlight] }); ``` Which renders as: ```ts const config = defineConfig({ title: 'My Library' // [!code highlight] }); ``` ## Additions and deletions Mark a line with `// [!code ++]` for an addition or `// [!code --]` for a deletion. They render with a colored background and a `+` / `-` gutter marker. You write: ```text export default { preprocess: [vitePreprocess()], // [!code --] preprocess: [vitePreprocess(), docsmith()], // [!code ++] }; ``` Which renders as: ```ts export default { preprocess: [vitePreprocess()], // [!code --] preprocess: [vitePreprocess(), docsmith()] // [!code ++] }; ``` ## Focus `// [!code focus]` dims the other lines so the eye lands on what matters. Hover the rendered block to bring the rest back. You write: ```text function setup() { const app = createApp(); app.use(docsmith()); // [!code focus] return app; } ``` Which renders as: ```ts function setup() { const app = createApp(); app.use(docsmith()); // [!code focus] return app; } ``` ## Errors and warnings `// [!code error]` and `// [!code warning]` tint a line red or amber, for call-outs like a deprecated call or a footgun. You write: ```text const ok = readFile('./page.md'); const bad = readFile(); // [!code error] const risky = readFileSync('./page.md'); // [!code warning] ``` Which renders as: ```ts const ok = readFile('./page.md'); const bad = readFile(); // [!code error] const risky = readFileSync('./page.md'); // [!code warning] ``` ## Highlight a word `// [!code word:name]` highlights every occurrence of `name` on the next line. Add a count like `word:name:2` to limit how many. You write: ```text const name = frontmatter.title; // [!code word:name] ``` Which renders as: ```ts const name = frontmatter.title; // [!code word:name] ``` Write the marker inside a comment your language understands: `//` for JS/TS/Svelte, `#` for bash or YAML, `` for HTML. Shiki strips the comment along with the marker, so it never ships to the reader. (To show a marker literally instead of applying it, as the "You write" blocks above do, put it in a plain `text` block.) # Live Examples ## A real, running component The button below is a real Svelte component running as part of this app. It is not a screenshot, and not a sandboxed iframe. Click it, then open the source: ## Single source of truth The rendered component and the source panel above both come from **one file**, `counter.svelte`. It is imported twice: once as a component (rendered) and once as `?source` (highlighted at build time by the `docsmith()` plugin from `svelte-docsmith/vite`), so the demo and its code can never drift. The example in your docs is the same component your users import. When the component changes, the rendered demo and its shown source both change with it, so it can never decay into a screenshot of something that used to work. See [Writing pages](/docs/writing-pages) for the import pattern to copy. ## API reference The live, rendered component. Pre-highlighted source HTML. Pass a ?source import of the same file. # Theming DocSmith ships its entire look as shadcn-style design tokens behind a single stylesheet. You restyle the whole system by redefining tokens, not by touching components, so a rebrand is a handful of CSS variables instead of a fork. ## One import, one contract The style contract is one import on top of Tailwind: ```css @import 'tailwindcss'; @import 'svelte-docsmith/theme.css'; ``` `theme.css` does three things: it makes Tailwind scan the package so the components' utility classes are generated, it registers the shadcn token set for `:root` and `.dark`, and it pulls in the typography and animation plugins. Every component reads those tokens, so the tokens are the only surface you style. On its own, `theme.css` gives you the default theme, **Darkmatter**: a near-monochrome shell with a warm orange primary. You import nothing else to get it. ## The presets Eleven presets ship in the box. Pick one below to preview it, and toggle the site's dark mode to see both sides: A preset is a stylesheet that redefines the color tokens, and for some the corner radius, and nothing else. Import it after `theme.css` and it wins: ```css @import 'tailwindcss'; @import 'svelte-docsmith/theme.css'; @import 'svelte-docsmith/themes/amethyst.css'; ``` Darkmatter is already baked into `theme.css`, so you only import `themes/darkmatter.css` to return to it after trying another preset. Available: `darkmatter` (default), `tangerine`, `amethyst`, `graphite`, `evergreen`, `rose`, `ocean`, `nord`, `claude`, `bubblegum`, and `mono`. Each covers light and dark. Want your own brand color instead? Skip the preset and override the tokens directly. ## Overriding tokens Redefine any token after the import and it wins. Tokens are OKLCH, so change the primary and every button, link, and accent follows: ```css @import 'tailwindcss'; @import 'svelte-docsmith/theme.css'; :root { --primary: oklch(0.55 0.2 265); /* your brand color */ --radius: 0.5rem; /* tighter corners */ } ``` If an override is not taking effect, import order is almost always the cause. Your redefinition has to come after the `theme.css` import, or the package's own value wins. The same rule applies to preset stylesheets. ## The token set The tokens are standard shadcn. The ones you reach for most: | Token | Controls | | ------------------------------------ | ------------------------------------- | | `--background` / `--foreground` | Page surface and body text | | `--primary` / `--primary-foreground` | Brand color and text on it | | `--muted` / `--muted-foreground` | Quiet fills and secondary text | | `--accent` / `--accent-foreground` | Hover and highlight surfaces | | `--border` / `--input` / `--ring` | Hairlines, field borders, focus rings | | `--card` / `--popover` | Raised surfaces | | `--sidebar*` | The docs sidebar, tokened separately | | `--radius` | Corner rounding across the system | ## Dark mode Dark mode is class-based: every token has a `.dark` variant, and DocSmith styles respond to a `dark` class on the `` element. The docs site toggles it with [`mode-watcher`](https://github.com/svecosystem/mode-watcher). Drop its `` in your root layout and the theme toggle and system preference work out of the box. An override only covers the theme whose selector you write it under, so set a token for both modes to change both: ```css :root { --primary: oklch(0.55 0.2 265); } .dark { --primary: oklch(0.7 0.16 265); /* lighter, for the dark surface */ } ``` ## Fonts Three families are set as tokens: `--font-sans`, `--font-serif`, and `--font-mono`. Point them at your own faces and load the fonts however you normally would (a `` in `app.html`, `@fontsource`, or your host): ```css :root { --font-sans: 'Geist', sans-serif; --font-mono: 'Geist Mono', monospace; } ``` # Search DocSmith builds a full-text search index from your pages at build time and ships a ⌘K / Ctrl-K command palette that searches it. There is nothing to host and no service to configure. ## Enable it Pass a `search` loader to `DocsShell`. It hands back the generated index, which DocSmith lazily fetches the first time the palette opens, so it never weighs down your initial load: ```svelte import('svelte-docsmith/search').then((m) => m.docs)} > {@render children()} ``` That is the whole setup. A search button appears in the header, ⌘K (Ctrl-K on Windows and Linux) opens the palette from anywhere, and results link straight to the matching page. Omit the prop to leave search off. Passing a function that dynamically imports `svelte-docsmith/search` lets your bundler split the index into its own chunk. The index is fetched only when a reader first opens search, not on every page view. ## What gets indexed Each page contributes its `title`, its `h2`/`h3` headings, its frontmatter `description`, and its body text, reduced to plain prose, with code blocks, component markup, and markdown punctuation stripped out. Title and heading matches rank above body matches. ## A custom search UI The palette is the default, but the engine is exported if you want to build your own input. `createSearchEngine` takes the generated index and returns a `search(query, limit?)` that yields ranked results with a context snippet: ```ts import { createSearchEngine } from 'svelte-docsmith'; import { docs } from 'svelte-docsmith/search'; const engine = createSearchEngine(docs); for (const hit of engine.search('theming')) { console.log(hit.title, hit.path, hit.snippet); } ``` # SEO `DocsShell` writes the head tags for every page (``, meta description, canonical URL, and Open Graph / Twitter Card tags) with no per-page wiring. Doc pages get theirs straight from frontmatter. ## Per-page, from frontmatter A page's `title` becomes `Page · Site Title`, and its `description` becomes the meta and social description. You already write both to drive the sidebar, so there is nothing extra to add: ```md --- title: Installation description: Add Svelte DocSmith to a SvelteKit project. section: Getting Started order: 2 --- ``` ## Site-wide defaults Set the defaults once in your `DocsmithConfig`. `url` is the piece that unlocks absolute links (a canonical `<link>` and absolute `og:url`/image), so search engines and social scrapers resolve them correctly: ```ts export const siteConfig = defineConfig({ title: 'My Library', description: 'A short tagline, used when a page has no description.', url: 'https://my-library.dev', ogImage: '/og.png' // absolute, or resolved against `url` }); ``` | Field | Used for | | ------------- | -------------------------------------------------------------------- | | `description` | Default meta description for pages without their own | | `url` | Canonical origin; enables `<link rel="canonical">` and absolute URLs | | `ogImage` | Default social-share image | <Callout variant="note" title="A canonical URL needs an origin"> Without `url`, DocSmith can't build an absolute address, so it omits the canonical and `og:url` tags rather than emit a wrong one. Set `url` to your deployed origin to turn them on. </Callout> ## Non-doc pages Pages that aren't markdown (a landing page, a custom route) have no frontmatter, so pass the `seo` prop to set or override the head: ```svelte <DocsShell {config} layout="page" seo={{ title: 'Themes', description: 'Preview the built-in themes.' }} > {@render children()} </DocsShell> ``` ## Sitemap `generateSitemap` builds a `sitemap.xml` from your content index. Add a `src/routes/sitemap.xml/+server.ts`: ```ts import { docs } from 'svelte-docsmith/content'; import { generateSitemap } from 'svelte-docsmith'; import { siteConfig } from '$lib/site-config'; export const prerender = true; export function GET() { const body = generateSitemap(siteConfig.url ?? '', [ { path: '/' }, ...docs.map((d) => ({ path: d.path, lastmod: d.lastUpdated })) ]); return new Response(body, { headers: { 'content-type': 'application/xml' } }); } ``` Each entry gets a `<lastmod>` from the page's last git commit. Then point crawlers at it from `static/robots.txt`: ```txt User-agent: * Allow: / Sitemap: https://your-docs.dev/sitemap.xml ``` ## llms.txt The [llms.txt](https://llmstxt.org) standard gives AI tools a clean, plain-text view of your docs. Svelte DocSmith generates the data at build time in the `svelte-docsmith/llms` module, and two helpers turn it into the two files the standard defines: `llms.txt` (a curated index of links) and `llms-full.txt` (the full text of every page). Add `src/routes/llms.txt/+server.ts`: ```ts import { docs } from 'svelte-docsmith/llms'; import { generateLlmsTxt } from 'svelte-docsmith'; import { siteConfig } from '$lib/site-config'; export const prerender = true; export function GET() { const body = generateLlmsTxt( { title: siteConfig.title, description: siteConfig.description, origin: siteConfig.url }, docs ); return new Response(body, { headers: { 'content-type': 'text/plain; charset=utf-8' } }); } ``` And `src/routes/llms-full.txt/+server.ts`, identical but for `generateLlmsFullTxt`: ```ts import { docs } from 'svelte-docsmith/llms'; import { generateLlmsFullTxt } from 'svelte-docsmith'; import { siteConfig } from '$lib/site-config'; export const prerender = true; export function GET() { const body = generateLlmsFullTxt( { title: siteConfig.title, description: siteConfig.description, origin: siteConfig.url }, docs ); return new Response(body, { headers: { 'content-type': 'text/plain; charset=utf-8' } }); } ``` Both follow your sidebar reading order, grouping pages by `section` and sorting by `order`. Each page's title becomes an `h1`, and its `description` frontmatter annotates the link in the index. <Callout variant="tip"> You are reading these docs through this exact pipeline. Open [/llms.txt](/llms.txt) and [/llms-full.txt](/llms-full.txt) to see the output. </Callout> ## Copy page The same per-page markdown powers a "Copy page" button on every doc page. Turn it on with the `copyPage` prop on `DocsShell`: ```svelte <DocsShell {config} content={docs} copyPage> {@render children()} </DocsShell> ``` The split button copies the page as Markdown, and its dropdown links to the raw `.md`, or opens the page in ChatGPT or Claude. It expects each page to be available at `<path>.md`, so add one catch-all endpoint, `src/routes/[...slug].md/+server.ts`: ```ts import { docs } from 'svelte-docsmith/llms'; import { error } from '@sveltejs/kit'; import type { EntryGenerator, RequestHandler } from './$types'; export const prerender = true; export const entries: EntryGenerator = () => docs.map((doc) => ({ slug: doc.path.replace(/^\//, '') })); export const GET: RequestHandler = ({ params }) => { const doc = docs.find((d) => d.path === `/${params.slug}`); if (!doc) error(404, 'Not found'); return new Response(doc.content, { headers: { 'content-type': 'text/markdown; charset=utf-8' } }); }; ``` <Callout variant="tip"> Try it: the "Copy page" button at the top of this page, or open [this page as Markdown](/docs/seo.md). </Callout> # Callout Highlight something the reader shouldn't miss. Four intents, each with its own icon and color. The body stays on the page's normal text color so it's always legible. <Callout variant="note"> This is a **note**, neutral informational context. </Callout> <Callout variant="tip"> This is a **tip**, a helpful shortcut or best practice. </Callout> <Callout variant="warning"> This is a **warning**, so proceed carefully. </Callout> <Callout variant="danger"> This is a **danger** callout: something here can break or lose data. </Callout> ## Usage Import it and pick a `variant`. The default is `note`; pass `title` to override the heading. Leave **blank lines** around the content so mdsvex parses the markdown inside (bold, links, code). Without them it renders as literal text. <!-- prettier-ignore --> ```svelte <Callout variant="tip" title="One more thing"> You can override the heading with the `title` prop, and use **markdown** inside, including `code` and [links](/docs/theming). </Callout> ``` ## API reference <PropsTable> <Prop name="variant" type="'note' | 'tip' | 'warning' | 'danger'" default="'note'"> Visual intent. </Prop> <Prop name="title" type="string" default="the variant"> Heading above the body. </Prop> </PropsTable> # Steps A numbered walkthrough (a connecting line with numbered badges) for setup flows where the order matters. Each step is a `<Step>`; the numbers are automatic. <Steps> <Step title="Install the package"> Add `svelte-docsmith` to your SvelteKit project. </Step> <Step title="Register the pipeline"> Add `docsmith()` in `svelte.config.js` and `vite.config.ts`. </Step> <Step title="Write a page"> Drop a `+page.md` under `src/routes/docs/` and it appears in the sidebar. </Step> </Steps> ## Usage `Steps` and `Step` are plain components. They work in a markdown page **and** in any `.svelte` file, with no preprocessor required. Give each `Step` an optional `title`; leave blank lines around markdown content so it's parsed. <!-- prettier-ignore --> ```svelte <Steps> <Step title="First">Do this.</Step> <Step title="Then">Do that.</Step> </Steps> ``` ### Markdown shortcut Inside a markdown page only, you can skip the `<Step>` tags and use a plain ordered list, and mdsvex turns it into steps: <!-- prettier-ignore --> ```md <Steps> 1. Do this. 2. Do that. </Steps> ``` ## API reference ### Steps Wraps its `<Step>` children; no other props. ### Step <PropsTable> <Prop name="title" type="string"> Optional heading for the step. </Prop> </PropsTable> # Card & CardGrid A `Card` groups a title and description, optionally as a link. Give it an `href` and it becomes clickable, with a hover state and a trailing arrow. `CardGrid` lays cards out in a responsive grid that reflows without breakpoints: as many columns as fit, down to one on narrow screens. <CardGrid> <Card title="Introduction" href="/docs/introduction"> What Svelte DocSmith is and who it's for. </Card> <Card title="Quick Start" href="/docs/quick-start"> Wire up the pipeline and render your first page. </Card> <Card title="Theming" href="/docs/theming"> Override tokens or pick a pre-installed theme. </Card> </CardGrid> ## Usage ```svelte <CardGrid> <Card title="Quick Start" href="/docs/quick-start"> Wire up the pipeline and render your first page. </Card> <Card title="External link" href="https://svelte.dev" external>Opens in a new tab.</Card> </CardGrid> ``` ## API reference ### Card <PropsTable> <Prop name="title" type="string" required> Card heading. </Prop> <Prop name="href" type="string"> Makes the card a link. </Prop> <Prop name="external" type="boolean" default="false"> Open the link in a new tab. </Prop> <Prop name="icon" type="Snippet"> Optional leading icon. </Prop> </PropsTable> ### CardGrid <PropsTable> <Prop name="children" type="Snippet"> The Cards to lay out. </Prop> </PropsTable> # Tabs Group alternatives such as package managers, framework variants, or OS-specific commands so the reader sees one at a time. Give each `TabItem` a `label`; `Tabs` builds the tab row from them, so there is nothing to keep in sync. <Tabs> <TabItem label="npm"> ```bash npm i -D svelte-docsmith ``` </TabItem> <TabItem label="pnpm"> ```bash pnpm add -D svelte-docsmith ``` </TabItem> <TabItem label="yarn"> ```bash yarn add -D svelte-docsmith ``` </TabItem> </Tabs> ## Usage Leave blank lines around the content inside each `TabItem` so the markdown (code fences, prose) is parsed. The first tab is selected by default; pass `value` on `Tabs` to start on a different one. <!-- prettier-ignore --> ````svelte <Tabs> <TabItem label="npm"> ```bash npm i -D svelte-docsmith ``` </TabItem> <TabItem label="pnpm"> ```bash pnpm add -D svelte-docsmith ``` </TabItem> </Tabs> ```` ## Synced tabs Give related `Tabs` the same `syncKey` and they share one selection: pick `pnpm` in any block and every block with that key switches to `pnpm`, across the page and the rest of the site. The choice is remembered across reloads. Use it for package managers, runtimes, or any choice a reader makes once and keeps. Try it. These two blocks share `syncKey="demo-pm"`; changing one moves the other: <Tabs syncKey="demo-pm"> <TabItem label="npm"> ```bash npm i -D svelte-docsmith ``` </TabItem> <TabItem label="pnpm"> ```bash pnpm add -D svelte-docsmith ``` </TabItem> <TabItem label="yarn"> ```bash yarn add -D svelte-docsmith ``` </TabItem> </Tabs> <Tabs syncKey="demo-pm"> <TabItem label="npm"> ```bash npx sv add svelte-docsmith ``` </TabItem> <TabItem label="pnpm"> ```bash pnpm dlx sv add svelte-docsmith ``` </TabItem> <TabItem label="yarn"> ```bash yarn dlx sv add svelte-docsmith ``` </TabItem> </Tabs> ## API reference ### Tabs <PropsTable> <Prop name="value" type="string" default="first tab"> Label of the tab selected by default. </Prop> <Prop name="syncKey" type="string"> Sync group. Blocks with the same key share their selection and remember it across reloads. </Prop> </PropsTable> ### TabItem <PropsTable> <Prop name="label" type="string" required> The tab's trigger text. </Prop> <Prop name="value" type="string" default="label"> Underlying value, only needed to disambiguate duplicate labels. </Prop> </PropsTable> # Accordion Fold FAQs, optional details, or long asides behind a heading the reader expands on demand, one panel at a time by default, so the page stays scannable. <Accordion> <AccordionItem title="What is svelte-docsmith?"> A documentation framework for Svelte 5. Your markdown files _are_ SvelteKit routes, so examples run as part of one real app. </AccordionItem> <AccordionItem title="Do I have to configure the sidebar?"> No. Nav is derived from each page's frontmatter, never hand-written. </AccordionItem> <AccordionItem title="Can I open several panels at once?"> Yes. Pass `multiple` to the `Accordion` and every panel toggles independently. </AccordionItem> </Accordion> ## Usage Each `AccordionItem` takes a `title` (the always-visible summary) and claims its own value automatically, so you never wire up ids. Leave **blank lines** around the panel content so mdsvex parses the markdown inside. <!-- prettier-ignore --> ```svelte <Accordion> <AccordionItem title="First question"> Answer with full **markdown**, including `code` and [links](/docs/theming). </AccordionItem> <AccordionItem title="Second question"> Another answer. </AccordionItem> </Accordion> ``` Pass `multiple` to let panels stay open independently: ```svelte <Accordion multiple> <!-- items --> </Accordion> ``` ## API reference ### Accordion <PropsTable> <Prop name="multiple" type="boolean" default="false"> Allow several panels open at once. </Prop> </PropsTable> ### AccordionItem <PropsTable> <Prop name="title" type="string" required> The heading that toggles the panel. </Prop> </PropsTable> # Badge Label a status, version, or category inline. Six intents cover the usual docs needs, neutral through danger, each driven by your theme tokens so they recolor with the rest of the site. <p class="not-prose flex flex-wrap gap-2"> <Badge>default</Badge> <Badge variant="primary">primary</Badge> <Badge variant="secondary">secondary</Badge> <Badge variant="success">success</Badge> <Badge variant="warning">warning</Badge> <Badge variant="danger">danger</Badge> <Badge variant="outline">outline</Badge> </p> ## Usage Import it and pick a `variant`. The default is `default`; pass `href` to turn the badge into a link. ```svelte <Badge variant="success">Stable</Badge> <Badge variant="warning">Beta</Badge> <Badge variant="primary" href="/docs/introduction">New</Badge> ``` ## API reference <PropsTable> <Prop name="variant" type="'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'outline'" default="'default'"> Visual intent. </Prop> <Prop name="href" type="string"> Renders the badge as a link. </Prop> <Prop name="external" type="boolean" default="false"> For a linked badge, open in a new tab with <code>rel="noopener noreferrer"</code>. </Prop> </PropsTable> # Kbd Show a key or shortcut the way a keyboard does: a small, raised cap that reads as _press this_, not as code. <p class="not-prose"> Save with <Kbd>⌘</Kbd> <Kbd>S</Kbd>, or open the palette with <Kbd>Ctrl</Kbd> <Kbd>K</Kbd>. </p> ## Usage Wrap each key in its own `Kbd`; combine several for a chord. ```svelte <p>Press <Kbd>Ctrl</Kbd> <Kbd>K</Kbd> to search.</p> ``` ## API reference <PropsTable> <Prop name="children" type="Snippet"> The key label to show. </Prop> </PropsTable> # File Tree Sketch a folder layout so the reader sees where a file goes. Indentation and folder icons carry the structure, no ASCII art to keep aligned by hand. <FileTree> <FileTreeItem name="src" folder> <FileTreeItem name="routes"> <FileTreeItem name="docs"> <FileTreeItem name="+layout.svelte" /> <FileTreeItem name="introduction/+page.md" highlight /> </FileTreeItem> </FileTreeItem> <FileTreeItem name="app.css" /> </FileTreeItem> <FileTreeItem name="svelte.config.js" /> <FileTreeItem name="package.json" /> </FileTree> ## Usage Nest `FileTreeItem`s to nest directories; an item with children is a folder automatically. Pass `folder` to style an empty directory, and `highlight` to call out the entry a guide is about to touch. <!-- prettier-ignore --> ```svelte <FileTree> <FileTreeItem name="src" folder> <FileTreeItem name="app.css" highlight /> </FileTreeItem> <FileTreeItem name="package.json" /> </FileTree> ``` ## API reference ### FileTree Wraps its `<FileTreeItem>` children; no other props. ### FileTreeItem <PropsTable> <Prop name="name" type="string" required> File or directory name. </Prop> <Prop name="folder" type="boolean" default="false"> Force folder styling for an empty directory. </Prop> <Prop name="highlight" type="boolean" default="false"> Emphasize the entry. </Prop> </PropsTable> # Props Table Document a component's props the same way on every page: name, type, and description in one styled table, so the API reference reads consistently instead of drifting per author. <PropsTable> <Prop name="variant" type="'default' | 'primary'" default="'default'"> Visual intent of the component. </Prop> <Prop name="href" type="string" required> Turns the element into a link. </Prop> <Prop name="disabled" type="boolean" default="false"> Prevents interaction. </Prop> </PropsTable> ## Usage Wrap `Prop` rows in a `PropsTable`. Give each `Prop` a `name`; add `type`, `default`, or `required` as needed, and put the description in the body. <!-- prettier-ignore --> ```svelte <PropsTable> <Prop name="variant" type="'default' | 'primary'" default="'default'"> Visual intent of the component. </Prop> <Prop name="href" type="string" required> Turns the element into a link. </Prop> </PropsTable> ``` ## API reference ### PropsTable <PropsTable> <Prop name="title" type="string"> Optional caption, e.g. the component name when a page documents several. </Prop> </PropsTable> ### Prop <PropsTable> <Prop name="name" type="string" required> Property name. </Prop> <Prop name="type" type="string"> Type signature, shown as code. </Prop> <Prop name="default" type="string"> Default value, shown beside the type. </Prop> <Prop name="required" type="boolean" default="false"> Marks the property as required. </Prop> </PropsTable>