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:
sdocs.config.tssdocs.config.mjssdocs.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  (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
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
docssection (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: falseTurning 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
includeglobs with.sdocswapped 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:
min0.75,max1.5,default1,step0.05,var--scale,labelScale
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:
| kind | option | default | applies to |
|---|---|---|---|
doc | maxWidth | 1200px | the doc's content column |
doc | padding | 32px | space around the doc prose |
doc | toc | true | table-of-contents visibility |
doc | contentX | left | aligns the content column: left/center/right |
page | maxWidth | 1200px | the page's content container |
page | padding | 32px | space around the page content |
page | contentX | left | places the container: left/center/right |
showcase | maxWidth | 1200px | the showcase content column |
showcase | padding | 16px | preview & example stages (in [SHOWCASE] and [DOC]) |
showcase | direction | row | stage flex-direction |
showcase | gap | 16px | stage gap |
showcase | contentX | left | horizontal: left/center/right/justify |
showcase | contentY | top | vertical: top/middle/bottom/justify |
showcase | background | none | stage background — a CSS color or a var() from the project's css |
showcase | minHeight | none | minimum stage height; reserves room for content that overflows the stage, like an open dropdown |
layout | maxWidth | 100% | the full-page stage |
layout | padding | 0px | space inside the stage |
layout | background | none | stage background — a CSS color or a var() from the project's css |
layout | minHeight | none | minimum 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',
},
};