Skip to content
sdocs

Configuration

sdocs reads its configuration from sdocs.config.js (or .ts / .mjs) in the project root. When used as a Vite plugin, options can also be passed directly to sdocsPlugin() — those override the config file.

Config file lookup

Checked in order:

  1. sdocs.config.ts
  2. sdocs.config.mjs
  3. sdocs.config.js

First one found is used. Missing config is fine — sdocs applies defaults.

A .ts config loads only on Node versions with native TypeScript type stripping; on older Node it throws. Since .ts is checked first, prefer .js or .mjs unless your Node supports it.

The config is an ES module — it ends in export default. A .js file is read as one only when the nearest package.json says "type": "module"; without that, Node reads it as a CommonJS script and the export is a syntax error. .mjs is read as a module either way, which is why sdocs init writes sdocs.config.mjs unless the project is already ESM.

Full schema

interface SdocsConfig {
  include?: string | string[];
  port?: number;
  open?: boolean;
  css?: string | Record<string, string>;
  title?: string;
  logo?: string | false;
  favicon?: string;
  sections?: ({ slug: string; title?: string; order?: string[] } | { type: 'divider' })[];
  home?: string;
  routing?: 'history' | 'hash';
  base?: string;
  outDir?: string;
  mcp?: boolean;
  components?: string | string[];
  axes?: { id: string; label?: string; values: string[] }[];
  scale?: { min?: number; max?: number; default?: number; step?: number; var?: string; label?: string; presets?: { label: string; value: number }[] };
  static?: string;
  content?: {
    doc?: { maxWidth?: string; padding?: string; toc?: boolean; contentX?: string };
    page?: { maxWidth?: string; padding?: string; contentX?: string };
    showcase?: { maxWidth?: string; padding?: string; direction?: string; gap?: string; contentX?: string; contentY?: string; background?: string; minHeight?: string };
    layout?: { maxWidth?: string; padding?: string; background?: string; minHeight?: string };
  };
}

include

Glob(s) matching .sdoc files.

  • Type: string | string[]
  • Default: ['./src/**/*.sdoc']

Relative paths are resolved against the project root. Absolute paths are used as-is.

include: ['./src/**/*.sdoc', './packages/**/*.sdoc']

port

Dev server port.

  • Type: number
  • Default: 3000

Only used by the standalone CLI (sdocs dev and sdocs preview). Ignored when embedded as a Vite plugin.

open

Whether to open the browser when sdocs dev or sdocs preview starts.

  • Type: boolean
  • Default: false

css

Stylesheet(s) loaded into every stage — preview and example iframes and [LAYOUT] pages. Stages are the only place this css loads: page prose and the docs app chrome keep their own styling, so the boundary between "your product" and "the documentation" stays crisp.

  • Type: string | Record<string, string>
  • Default: null

Single stylesheet:

css: './src/styles/global.css'

Named stylesheets (user switches between them via a dropdown):

css: {
  light: './src/styles/light.css',
  dark: './src/styles/dark.css',
}

Relative paths resolve from the project root. Absolute paths and http(s):// URLs are used as-is.

In embedded production builds the file is copied verbatim into the build output, so keep it self-contained: @import and relative url() references won't resolve from the copied location. Inline what the previews need (fonts can be data URIs) or use absolute http(s):// URLs.

See theming for details on named stylesheets.

static

A folder of static assets, served at the site root in dev and copied into dist/ by sdocs build — images for pages, files for previews.

  • Type: string
  • Default: none
static: './static'

With static/hero.png in the project, a page writes ![hero](/hero.png) (prose URLs get the build's base applied automatically). Inside preview/example stages, reference assets base-relative — no leading slash: src="hero.png", url('hero.png'), a path="icons/x.svg" prop. The stage page carries a <base href> set to the build's base, so those resolve correctly in dev and in a build deployed under a sub-path (--base "/repo/") alike; a root-absolute /hero.png means the domain root and breaks under a sub-path. One exception: a relative url() inside a CSS custom property resolves against the stylesheet that uses the var — in a build, the emitted css asset — so pass asset backgrounds via an inline style attribute (or resolve to an absolute URL in script with new URL(path, document.baseURI)). The option powers the standalone CLI flows (sdocs dev/run/build); when embedding the Vite plugin in an app, use the host's own public directory instead.

title

Text shown in the top bar, next to the logo.

  • Type: string
  • Default: 'sdocs'

Logo shown next to the title text.

  • Type: string | false
  • Default: 'sdocs'

'sdocs' shows the built-in sdocs mascot. Any other string is used as an image URL (/logo.svg from your static assets, or a full http(s):// URL). false hides the logo. A root-absolute path is prefixed with base automatically, so it resolves under a sub-path deploy.

logo: '/acme-logo.svg'

favicon

The browser-tab icon.

  • Type: string
  • Default: the built-in sdocs icon

A path (/logo.svg from your static folder) or full URL; point it at the same file as logo for a matching mark. Base-prefixed like other assets on build.

favicon: '/logo.svg'

sections

The site's sections, declared in top-bar order. Each has a URL-safe slug (its identity — the first route segment, and what titles reference via title="@slug/…"), an optional title for the tab (defaults to the capitalized slug), and an optional order array of route paths relative to the section — listed items sort first at their level, everything else follows alphabetically.

  • Type: ({ slug, title?, order? } | { type: 'divider' })[]
  • Default: none — a single implicit docs section (the top bar shows its lone tab)
sections: [
  { slug: 'guides', title: 'Guides', order: ['introduction', 'colors'] },
  { slug: 'components' },
]

Grouping tabs with a divider

A { type: 'divider' } entry draws a thin rule between the tabs on either side of it — for setting a section apart from the ones before it:

sections: [
  { slug: 'guides' },
  { slug: 'components' },
  { type: 'divider' },
  { slug: 'playground' },
]

A divider is not a section: it has no slug, no routes and no sidebar, and nothing can be titled into it. One with no section before it — or none after — draws nothing, so reordering the array never leaves a rule dangling at either end of the bar.

Referencing an undeclared section (or writing an unprefixed title when no docs section is declared) is an error: the Explorer shows it full-page and sdocs build fails.

home

Route path of the landing page — what the root URL and the logo show.

  • Type: string
  • Default: none — the root shows the About page
home: 'guides/introduction'

The path must resolve to an entity (an unresolvable home is an error). The home entity stays listed in its section's sidebar; add hide to its opener to keep it reachable only via the logo.

routing

URL style.

  • Type: 'history' | 'hash'
  • Default: 'history' in the standalone CLI, 'hash' when embedding

'history' uses real paths (/guides/installation) — the CLI dev server falls back to the app shell for any path, and sdocs build emits a physical index.html per route so static hosts need no rewrite rules. 'hash' uses #/ URLs, which work under any host routing — the right choice (and the default) when embedding.

base

The public base path the built site is served under — set it when the site lives under a sub-path rather than a domain root.

  • Type: string
  • Default: '/'

It's normalized to a leading and trailing slash (my-project/my-project/) and applies to sdocs build only; sdocs dev always serves at the root. Asset URLs and history routes are prefixed with it. A GitHub project Pages site is served at https://<owner>.github.io/<repo>/, so set base: '/<repo>/' (or pass --base on the CLI — handy for deriving it from the repo name in CI):

base: '/my-project/'

sdocs build also writes a 404.html (a copy of the shell), so an unknown deep link on a static host answers with a real 404 status and still boots the app — which then shows a not-found page naming the address it could not resolve, and links back into each section.

outDir

Where sdocs build writes the finished site.

  • Type: string
  • Default: 'dist'

Relative to the project root. The build empties this directory first, which is why it is worth setting: a component library documenting itself already uses dist/ for its own published bundle. Rather than delete it, the build stops and names the files it found:

$ npx sdocs build
[sdocs] dist/ already has files in it, and they were not put there by sdocs.
  Building would delete them. Point sdocs somewhere of its own:

    // sdocs.config.js
    export default { outDir: 'docs-dist' };

  Or empty dist/ yourself if its contents are disposable.

Directories a previous sdocs build wrote are marked, and rebuild without a word. --out-dir <dir> overrides this from the CLI.

outDir: 'docs-dist'

mcp

Whether sdocs dev serves the MCP server — the /mcp endpoint and the MCP button in the top bar (next to the theme toggle), which opens the connection info: the HTTP endpoint, the stdio command, and the tool list.

  • Type: boolean
  • Default: true
mcp: false

Turning it off removes the endpoint and the button. The explicit npx sdocs mcp command is unaffected, and built sites never serve an MCP endpoint either way.

components

Glob(s) locating your component sources — what sdocs coverage measures documentation against.

  • Type: string | string[]
  • Default: the include globs with .sdoc swapped for .svelte

The default is right whenever docs sit next to their components (./src/**/*.sdoc./src/**/*.svelte). Set it when they don't, or to narrow coverage to your public API:

components: './src/lib/components/**/*.svelte'

axes

Dimensions of your design system the reader can switch between — theme, density, palette. Each becomes a control in the top bar: a segmented control while there's room, a dropdown once there isn't.

  • Type: { id: string; label?: string; values: string[] }[]
  • Default: [] (no controls)
axes: [
  { id: 'scheme',  label: 'Theme',   values: ['light', 'dark'] },
  { id: 'density', label: 'Density', values: ['airy', 'compact'] },
]

The selection lands on every preview, example and layout as a data- attribute on the stage document's root — <html data-scheme="dark"> — and your own CSS gives it meaning:

[data-scheme="dark"] { color-scheme: dark; --color-bg: #0f1115; }

The first value listed is the default, and label falls back to the capitalized id. An id is lowercase letters, digits and dashes; sdocs- is reserved. Picks persist to localStorage and are validated against this config on load, so renaming a value doesn't strand returning readers.

See Theming for the full picture, including how axes compare to named stylesheets.

scale

A continuous knob, rendered as a slider in the top bar beside the axis controls. Its value lands on every stage as a CSS custom property.

  • Type: { min?, max?, default?, step?, var?, label?, presets? }
  • Defaults: min 0.75, max 1.5, default 1, step 0.05, var --scale, label Scale
scale: { min: 0.75, max: 1.5, default: 1, step: 0.05 }
.card { padding: calc(8px * var(--scale, 1)); }

presets adds named stops — [{ label: 'S', value: 0.875 }] — as a segmented control beside the slider; a value outside the range is refused.

A property rather than an attribute because a range has no set of names to key off: one rule reading a number beats a rule per step. Double-click the slider to return to the default. See Theming.

content

Content sizing per entity kind. Any CSS length works; padding takes CSS shorthand.

  • Type: { doc?, page?, showcase?, layout? }, each { maxWidth?: string; padding?: string }
  • Defaults:
kindoptiondefaultapplies to
docmaxWidth1200pxthe doc's content column
docpadding32pxspace around the doc prose
doctoctruetable-of-contents visibility
doccontentXleftaligns the content column: left/center/right
pagemaxWidth1200pxthe page's content container
pagepadding32pxspace around the page content
pagecontentXleftplaces the container: left/center/right
showcasemaxWidth1200pxthe showcase content column
showcasepadding16pxpreview & example stages (in [SHOWCASE] and [DOC])
showcasedirectionrowstage flex-direction
showcasegap16pxstage gap
showcasecontentXlefthorizontal: left/center/right/justify
showcasecontentYtopvertical: top/middle/bottom/justify
showcasebackgroundnonestage background — a CSS color or a var() from the project's css
showcaseminHeightnoneminimum stage height; reserves room for content that overflows the stage, like an open dropdown
layoutmaxWidth100%the full-page stage
layoutpadding0pxspace inside the stage
layoutbackgroundnonestage background — a CSS color or a var() from the project's css
layoutminHeightnoneminimum stage height — any CSS length

A doc's maxWidth constrains the content column together with its table of contents; hide the toc and the prose takes the full width. contentX places that column inside the view.

Preview and example stages are flex containers: items line up along direction with gap between them (wrapping as needed). contentX (horizontal) and contentY (vertical) are physical — sdocs maps each to the right flex property for the current direction, so contentX="center" centers horizontally whether the stage is a row or a column. justify spreads items apart (space-between).

content: {
  doc: { maxWidth: '900px', toc: false },
  showcase: { padding: '24px', direction: 'column' },
}

Entities and blocks override these in place with the same attributes: [DOC title="…" maxWidth="800px" toc="false"], [COMPONENT component={X} direction="column" gap="8px" background="var(--bg)" minHeight="240px"] — block beats entity beats config. minHeight is handy when a component opens content beyond its box — an open dropdown or popover — that the auto-sized preview would clip.

Full example

/** @type {import('sdocs').SdocsConfig} */
export default {
  include: ['./src/lib/**/*.sdoc'],
  port: 3001,
  open: true,
  title: 'Acme Design System',
  sections: [
    { slug: 'guides', title: 'Guides', order: ['getting-started'] },
    { slug: 'components', order: ['button', 'input', 'select'] },
  ],
  home: 'guides/getting-started',
  css: {
    light: './src/styles/light.css',
    dark: './src/styles/dark.css',
  },
};

See also

  • Sidebar — ordering, groups, and search
  • Theming — named stylesheets and light/dark
  • Types — full SdocsConfig type