# 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 the documentation framework for Svelte 5 library authors. Your interactive examples live inside one real, stateful SvelteKit app — not sandboxed as isolated islands, and not screenshots of a component that used to work. You write markdown under `src/routes/docs/`. DocSmith turns it into styled, navigable, syntax-highlighted pages, and lets you drop the same components your users import straight into the prose. ## Why another docs tool? A library's docs are only as good as their examples. Screenshots go stale. Sandboxed islands drift from the package your users install. When the example is the real component, running in the same app as the docs, that rot is gone by construction. That is why a DocSmith page is a real SvelteKit route: so the button, form, or chart in your docs is the same component your users import — running, stateful, and impossible to let rot. Live examples are the reason to adopt DocSmith. The scaffolding holds its own. Drop a page under `src/routes/docs/` and the sidebar builds itself from frontmatter — never a hand-maintained nav tree. The shell brings the header, mobile nav, in-page TOC, and prev/next links; the pipeline handles highlighting and anchors. You write content; the chrome and navigation keep up. ## Highlights - **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. - **Markdown as routes.** `.md` files compile to real Svelte components via mdsvex. No loader, no catch-all route. - **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 title="src/app.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 title="svelte.config.js" import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { docsmith } from 'svelte-docsmith/preprocess'; // [!code ++] export default { extensions: ['.svelte', '.md'], // [!code ++] preprocess: [vitePreprocess(), docsmith()], // [!code ++] 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 title="vite.config.ts" import { sveltekit } from '@sveltejs/kit/vite'; import tailwindcss from '@tailwindcss/vite'; import { docsmith } from 'svelte-docsmith/vite'; // [!code ++] import { defineConfig } from 'vite'; export default defineConfig({ plugins: [docsmith(), tailwindcss(), sveltekit()] // [!code ++] }); ``` 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 title="src/routes/docs/+layout.svelte" {@render children()} ``` Create `src/routes/docs/getting-started/+page.md`. The frontmatter drives the sidebar; everything below it is your content: ````md title="src/routes/docs/getting-started/+page.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`, `generateFeed`, `generateLlmsTxt`, `generateLlmsFullTxt`, 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, search, llms and changelog indexes, plus 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/llms`**: the generated per-page markdown index, exported as `docs` (see [SEO](/docs/seo)). - **`svelte-docsmith/changelog`**: the generated release index, exported as `releases` (see [Changelog](/docs/changelog)). - **`svelte-docsmith/mermaid`**: the diagram component, imported for you by a ` ```mermaid ` fence. You never import it directly. - **`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. ## docsmith() preprocessor From `svelte-docsmith/preprocess`, registered in `svelte.config.js`. It bundles mdsvex, Shiki, heading anchors and the page layout, so markdown compiles to real routes. Every option has a working default; pass none and it does the right thing. File extensions compiled as markdown. Shiki themes for the dual light/dark render of markdown code fences. The Vite plugin has its own themes for live example source; set both to the same pair or the two will not match. Extra Shiki languages on top of the built-in set. An unknown fence language falls back to plain text rather than failing the build. Absolute path to a custom mdsvex layout, or false for none. A custom layout must export a pre component from its module script, since code fences render through it. Number every code block. A fence overrides it either way with showLineNumbers or noLineNumbers. Enable Twoslash on fences marked twoslash. Needs the optional peer dependencies; see Code blocks. Extra remark plugins, appended after DocSmith's own. Extra rehype plugins, appended after DocSmith's own (slug, sectionize). ## docsmith() Vite plugin From `svelte-docsmith/vite`, added to `plugins` in `vite.config.ts`. It scans your pages into the generated indexes and serves the `?source` imports that [Live Examples](/docs/live-examples) use. Directory scanned for doc pages. Routes root, used to derive each page's URL from its location. Shiki themes for the ?source render behind Live Examples. Separate from the preprocessor's themes, because the two run from different config files; set them to the same pair or your example source will not match your code blocks. Path to the changelog that feeds the release index. Point it at the package you publish, or false to skip it. Route the changelog is served at. Used to build feed links and to find hand-written per-release pages. ## 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. The generated version manifest. Pass it to scope the sidebar, search, prev/next, and breadcrumbs to the version being read. See Versioning. Enable the ⌘K search palette by lazily providing the generated index, e.g. {'() => import(\'svelte-docsmith/search\').then((m) => m.docs)'}. Receives the active version id on a versioned site. 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 title="src/routes/+error.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. The version manifest, same as DocsShell. Pass it so an error under an archived prefix keeps that version's search scope and noindex. 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). - **`ResolvedVersion`**: one entry of the generated version manifest. See [Versioning](/docs/versioning). - **`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 language tags fall back to plain text rather than failing the build, so an unfamiliar fence 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. ## Diagrams Tag a fence `mermaid` and it renders as a diagram instead of code, via [Mermaid](https://mermaid.js.org), following the site's light and dark themes: ````md ```mermaid flowchart LR A[".md file"] --> B["mdsvex"] --> C["Svelte route"] ``` ```` renders as: ```mermaid flowchart LR A[".md file"] --> B["mdsvex"] --> C["Svelte route"] ``` Mermaid runs in the browser, so add it to your project. It's an optional peer dependency, pulled in only on pages that use it: ```bash npm i -D mermaid ``` A diagram is only as readable as its labels for anyone using a screen reader. Give one an accessible name and description with Mermaid's `accTitle` and `accDescr` directives, and both land in the rendered SVG: ````md ```mermaid flowchart LR accTitle: Markdown build pipeline accDescr: A .md file is processed by mdsvex, which produces a Svelte route. A[".md file"] --> B["mdsvex"] --> C["Svelte route"] ``` ```` If a diagram fails to parse, or `mermaid` isn't installed, the source is shown as a plain code block instead so the page never loses the content. ## 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.) ## Highlight lines by number Comment markers can't reach every line. Inside a Svelte template region an HTML comment is stripped without highlighting anything, so mark those lines from the fence itself instead. A single line, a list, or a range all work. ````md ```svelte {4} import('svelte-docsmith/search').then((m) => m.docs)} > {@render children()} ``` ```` renders as: ```svelte {4} import('svelte-docsmith/search').then((m) => m.docs)} > {@render children()} ``` Ranges and lists use the same syntax: `{2-4}`, `{1,5}`, or both together. ## Filenames and line numbers Add `title=` to label a block with the file it belongs to, and `showLineNumbers` to number it. Pair `startLine=` with numbering when the snippet is lifted out of a longer file, so the numbers match the real source. ````md ```ts title="vite.config.ts" showLineNumbers import { docsmith } from 'svelte-docsmith/vite'; export default { plugins: [docsmith()] }; ``` ```` renders as: ```ts title="vite.config.ts" showLineNumbers import { docsmith } from 'svelte-docsmith/vite'; export default { plugins: [docsmith()] }; ``` Numbering every block by default is a preprocessor option, and a single fence can still opt out with `noLineNumbers`: ```js title="svelte.config.js" docsmith({ lineNumbers: true }); ``` ## Real types on hover Add `twoslash` to a TypeScript or Svelte fence and the block is run through the TypeScript compiler, so hovering a token shows its actual inferred type rather than a hand-written guess. ````md ```ts twoslash const version = '0.9.0'; const parts = version.split('.').map(Number); ``` ```` renders as (hover `parts` or `version`): ```ts twoslash const version = '0.9.0'; const parts = version.split('.').map(Number); ``` Twoslash is opt-in twice over: enable it in the preprocessor, then mark the individual fences that want it. ```js title="svelte.config.js" docsmith({ twoslash: true }); ``` It needs three optional peer dependencies, pulled in only if you use it: ```bash npm i -D @shikijs/twoslash twoslash-svelte typescript ``` Because the snippet really is compiled, it has to typecheck: an unresolved import or a type error means there is no type to show. Rather than fail your build over one block, DocSmith falls back to an ordinary highlight and warns which block it was. Use `// @errors: 2322` to show an error deliberately, `// @noErrors` to silence one, and `// ---cut---` to hide setup lines from the rendered output while still compiling them. # 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 title="src/app.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 title="src/app.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 title="src/app.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 title="src/routes/docs/+layout.svelte" {12} 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 title="src/routes/sitemap.xml/+server.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 — or none, when the page has no date (no frontmatter `lastUpdated`, and git could not supply one). On a **shallow clone** (the default for `actions/checkout` and many hosts), DocSmith omits every git-derived date rather than stamp every page with the same build day, which would teach crawlers to ignore the field. Build from a full history (`fetch-depth: 0` in GitHub Actions) when you want real `<lastmod>` values, or set `lastUpdated` in frontmatter on pages that need one. Then point crawlers at the sitemap 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 title="src/routes/llms.txt/+server.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 title="src/routes/llms-full.txt/+server.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 title="src/routes/[...slug].md/+server.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> # Changelog Every release you ship already produces release notes. DocSmith reads them and publishes them, so the changelog on your site can never fall behind what actually went out. ## Where the entries come from The `docsmith()` plugin parses a `CHANGELOG.md` into releases and serves them as the `svelte-docsmith/changelog` module. The format Changesets writes is what it expects: an `## <version>` heading per release, `### Minor Changes` style groups, and one bullet per entry. Commit hashes are stripped, multi-paragraph entries survive intact, and each entry's markdown is rendered to HTML at build time so your site needs no runtime markdown renderer. Release dates come from git: the commit that first introduced each version's heading. No date is invented when the history is unavailable. ```ts title="vite.config.ts" // Defaults to CHANGELOG.md beside your app. In a monorepo, point it at the // package you actually publish. docsmith({ changelog: '../../packages/my-lib/CHANGELOG.md' }); ``` ## Add the page ```svelte title="src/routes/changelog/+page.svelte" <DocsShell config={siteConfig} content={docs} layout="page"> <Changelog {releases} description="What shipped in each release." /> </DocsShell> ``` ## Add the feed An Atom feed makes each release something readers can subscribe to rather than something they have to check for. ```ts title="src/routes/changelog.xml/+server.ts" import { releases } from 'svelte-docsmith/changelog'; import { generateFeed } from 'svelte-docsmith'; import { siteConfig } from '$lib/site-config'; export const prerender = true; export function GET() { const body = generateFeed(releases, { url: siteConfig.url, title: siteConfig.title }); return new Response(body, { headers: { 'content-type': 'application/atom+xml; charset=utf-8' } }); } ``` Atom rather than RSS, because it requires an unambiguous id and timestamp per entry, which a version and its release date give exactly. ## Write a proper post for a release Generated entries are terse by design. When a release deserves more, add `src/routes/changelog/<version>/` as a normal page and DocSmith links the release to it instead of to its anchor. Dots are awkward in directory names, so `0-9-0` works as well as `0.9.0`. Every other release stays generated, so nothing goes undocumented while you write up the ones that matter. # Versioning <Callout variant="warning" title="Experimental"> Versioning sits outside the 1.0 stability promise. `DocsVersions`, `currentOnly`, the archive marker, and `archive-version` may change in a minor release until authors have used them for real. An archive freezes prose, code samples and URLs — not what a page imports. Live examples on an archived page keep resolving to whatever the app currently has installed. </Callout> ## Why version your docs Ship a breaking major and two readers need different things: the one still on the old release needs its docs, and the one on the new release should not trip over stale pages. Versioning keeps the old release live at its own URL, scopes the sidebar and search to the version being read, and steers visitors who land on an archived page to the current one. It is opt-in, and it is designed so that turning it on changes nothing. Your current docs stay exactly where they are. ## The model The **current version** is the docs for your latest release. It lives at your docs root and is served unprefixed, so `/docs/theming` is `/docs/theming` forever. It is also the only version you edit. An **archived version** is a frozen copy of the docs for a superseded release, served under its own prefix at `/docs/v1/theming`. You never edit an archive. You create one at the moment you ship a breaking release, and then carry on editing the docs root. That means adopting versioning never moves a URL, and shipping a new release never moves one either. Only the archive is new. ```text src/routes/docs/ introduction/+page.md → /docs/introduction (current) theming/+page.md → /docs/theming (current) v1/introduction/+page.md → /docs/v1/introduction (archived) ``` ## Declare your versions Versions live in the `docsmith()` Vite plugin, because it scans them at build time to tag each page. ```ts // vite.config.ts import { docsmith } from 'svelte-docsmith/vite'; export default defineConfig({ plugins: [ docsmith({ versions: { current: { id: 'v2', label: 'v2' }, archived: [{ id: 'v1', label: 'v1' }] } }) // tailwindcss(), sveltekit() ] }); ``` Each archived version's `id` is both its folder name under your docs directory and its URL prefix. The current version has no folder of its own: its pages are the ones that are not inside an archive. Declaring only `current` is perfectly valid, and is how you start. Nothing renders differently until the first archive exists. ### What makes a valid id An id has to start with a letter or a digit, and can then hold letters, digits, `.`, `-` and `_`. So `v1`, `v1.0`, `2.x` and `next` are all fine, while `../evil`, `.hidden` and `api/v1` are rejected at build time. The rule applies to `current` as well as to archives, even though the current version's id never appears in a URL. Archiving turns today's current id into an archive folder, so an id that was legal only as `current` would fail one release later. ## When the config and your folders disagree Versions are declared by hand, so your config and your docs folder are two separate claims about which versions exist. The build checks that they agree and fails if they do not, because neither mismatch shows up in the output: an archive the config does not know about gets merged into your current docs, and a section folder wrongly declared as a version disappears from your current sidebar. Telling the two apart takes a marker. `archive-version` writes a `.docsmith-archive` file into every archive it creates, and the build reads it. A folder under your docs root is an archive when it has that file and is declared in `versions`. Any other combination is an error: | On disk | In `versions` | Result | | -------------- | ------------- | -------------------------------- | | Has the marker | Declared | Served as an archived version | | Has the marker | Not declared | Build fails: undeclared archive | | No marker | Declared | Build fails: not an archive | | No marker | Not declared | An ordinary section of your docs | <Callout variant="note" title="Copying an archive by hand"> If you create an archive folder yourself rather than with `archive-version`, add an empty `.docsmith-archive` file inside it. Without one the build treats it as part of your current docs and refuses to accept it as a version. </Callout> ## Wire it into the shell The plugin emits a `versions` manifest next to your content. Pass both to `DocsShell`. ```svelte <!-- src/routes/docs/+layout.svelte --> <DocsShell config={siteConfig} content={docs} {versions} search={() => import('svelte-docsmith/search').then((m) => m.docs)} > {@render children()} </DocsShell> ``` That is the whole wiring. No redirect, no route changes. `DocsShell` reads the active version from the URL and scopes the sidebar, search, prev/next, and breadcrumbs to it. If you use [`ErrorPage`](/docs/configuration#errorpage), pass it `versions` too. A 404 under an archived prefix then keeps that version's search scope and its `noindex`, instead of falling back to the current version. ## What readers get Once at least one archive exists, a **version switcher** appears in the header. It keeps readers on the same page when they switch, falling back to that version's home when the page has no equivalent there. On an archived version, a **banner** tells readers what they are looking at and links to the current equivalent. Archived pages also drop their "Edit this page" link, since the archive is frozen. ## Search engines see one version Archived docs should not compete with the current release in search results, but they are real content people still look for. So archives stay indexable with a self-canonical, while `sitemap.xml` and `llms.txt` list the current version only. Scope those endpoints with `currentOnly`: ```ts // src/routes/sitemap.xml/+server.ts import { docs, versions } from 'svelte-docsmith/content'; import { generateSitemap, currentOnly } from 'svelte-docsmith'; import { siteConfig } from '$lib/site-config'; export function GET() { const body = generateSitemap(siteConfig.url ?? '', [ { path: '/' }, ...currentOnly(docs, versions).map((d) => ({ path: d.path, lastmod: d.lastUpdated })) ]); return new Response(body, { headers: { 'content-type': 'application/xml' } }); } ``` To keep a version out of search engines entirely, set `noindex: true` on it. ## Archive a release When you ship a breaking release, freeze the docs that described the old one: ```bash npx svelte-docsmith archive-version v1 --label v1 ``` This copies your current docs into `v1/`, and does three things a plain copy would get wrong. It rewrites in-content links so they stay inside the archive, because an absolute link like `](/docs/theming)` would otherwise keep pointing at your newest docs. It writes each page's real last-updated date into its frontmatter, so the archive does not claim every page changed on the day you created it. And it marks the folder as an archive. Review the diff, then update your config to serve the archive and rename the current version: ```ts versions: { current: { id: 'v2', label: 'v2' }, archived: [{ id: 'v1', label: 'v1' }] } ``` Until you paste that in, the build fails and reports the new archive as undeclared. That is deliberate: it is the reminder, and it arrives while you are still looking at the terminal. The command prints the block to paste. ## What archives cost Worth knowing before you keep many of them around: - Archived pages are compiled by your current setup, so a breaking change to a docs component changes how every archive renders. - An archive freezes your content, not what it imports. A page importing a component from `$lib` or a package keeps resolving to whatever your app has installed, so a live example on an archived page demonstrates your current library rather than the version the page documents. `archive-version` lists the pages this applies to as it copies them. - Each archive adds its pages to the content index and its text to the search index, both of which grow linearly with the number of versions you keep. - Archive folders are real files in your repo, permanently. Keeping one or two archives is cheap. Keeping ten is a decision worth making deliberately. # 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> # Landing Page Five components compose the page in front of your docs: `Hero`, `FeatureGrid` with `Feature`, `CTA`, and the `Action` link the first and third share. Each one renders its own full-width `<section>` with the container, padding, and vertical rhythm already set, so a landing page is a handful of components rather than a wall of layout classes. They draw every colour from the same design tokens as your docs, so they follow your theme preset and both colour schemes with nothing to configure. ## Usage Pass `layout="page"` to `DocsShell` so the shell drops the sidebar and table of contents and gives the sections the full width of the viewport. ```svelte <DocsShell config={siteConfig} content={docs} layout="page" pattern> <Hero title="Your project" description="One line on what it does and who it's for."> {#snippet actions()} <Action href="/docs/quick-start">Get started</Action> <Action href="/docs/introduction" variant="secondary">Read the docs</Action> {/snippet} </Hero> <FeatureGrid background="muted" title="Everything a docs site needs"> <Feature title="Markdown routes">Add a file, get a page.</Feature> <Feature title="Search included">A command palette with no service to run.</Feature> <Feature title="Light and dark">Eleven presets ship built in.</Feature> </FeatureGrid> <CTA title="Start writing" description="Your first page is a markdown file."> {#snippet actions()} <Action href="/docs/quick-start">Quick start</Action> {/snippet} </CTA> </DocsShell> ``` `npm create svelte-docsmith` scaffolds a landing page built from these, so the fastest way to see them running is to start a project and edit the result. ## Hero The opening section. Give it a `media` snippet and it becomes a two-column split, with your headline on the left and a code sample, screenshot, or live component on the right. Leave `media` off and the hero centres on a single column instead, which suits a project that leads with a sentence rather than a screenshot. `title` and `description` each take a plain string or a snippet. Reach for the snippet form when you want to mark part of the headline up, such as colouring one word with `text-primary`. <PropsTable title="Hero"> <Prop name="title" type="string | Snippet" required> The headline. Pass a snippet to mark part of it up. </Prop> <Prop name="description" type="string | Snippet"> Supporting paragraph below the headline. </Prop> <Prop name="eyebrow" type="Snippet"> A pill, badge, or announcement link above the headline. </Prop> <Prop name="actions" type="Snippet"> Call-to-action buttons, usually one or two <code>Action</code> links. </Prop> <Prop name="media" type="Snippet"> The second column. Without it the hero centres on one column. </Prop> </PropsTable> ## Feature grid A grid of short capability blurbs. `FeatureGrid` owns the section heading and the layout; each `Feature` is one cell, with an optional icon rendered in a tinted square beside the text. Set `background="muted"` to tint the band and rule it top and bottom. Alternate it against the sections either side and the page reads as distinct bands rather than one continuous scroll. <PropsTable title="FeatureGrid"> <Prop name="title" type="string"> Section heading above the grid. </Prop> <Prop name="description" type="string"> Supporting line below the heading. </Prop> <Prop name="columns" type="2 | 3" default="3"> Columns at the widest breakpoint. The grid steps down to two, then one, on narrower screens. </Prop> <Prop name="background" type="'default' | 'muted'" default="'default'"> <code>muted</code> tints the band and adds a rule above and below. </Prop> <Prop name="children" type="Snippet" required> The <code>Feature</code> cells. </Prop> </PropsTable> <PropsTable title="Feature"> <Prop name="title" type="string" required> The cell heading. </Prop> <Prop name="icon" type="Snippet"> Optional leading icon, rendered in a tinted square. </Prop> <Prop name="children" type="Snippet"> The description. </Prop> </PropsTable> ## CTA The closing panel: a bordered card with a soft glow behind the heading, so the page ends on your primary colour instead of trailing off. Use `before` for a note that should sit above the heading (for example a pre-release `Callout`) so the section still ends on the actions; use `children` for content below them. <PropsTable title="CTA"> <Prop name="title" type="string" required> The closing heading. </Prop> <Prop name="description" type="string"> Supporting line below the heading. </Prop> <Prop name="actions" type="Snippet"> Call-to-action buttons. </Prop> <Prop name="before" type="Snippet"> Content above the heading — a pre-release note, for example — so the section can still end on the actions. </Prop> <Prop name="children" type="Snippet"> Content below the actions, such as a secondary note. </Prop> </PropsTable> ## Action The call-to-action link used by `Hero` and `CTA`. `primary` is the filled button, `secondary` the outlined one that sits beside it. The primary variant carries a trailing arrow that nudges on hover; turn it off with `arrow={false}`. It is deliberately small. Anything more elaborate, such as a button that copies an install command to the clipboard, is clearer written as your own markup inside the `actions` snippet. <PropsTable title="Action"> <Prop name="href" type="string" required> Where the link points. </Prop> <Prop name="variant" type="'primary' | 'secondary'" default="'primary'"> Filled or outlined. </Prop> <Prop name="external" type="boolean" default="false"> Open in a new tab with <code>rel="noopener noreferrer"</code>. </Prop> <Prop name="arrow" type="boolean"> Trailing arrow. Defaults to on for <code>primary</code>, off for <code>secondary</code>. </Prop> <Prop name="children" type="Snippet" required> The link label. </Prop> </PropsTable>