Styles
styles.colors
{ primary?, text? } — dark-mode overrides.
{
"styles": {
"colors": {
"primary": "#16a34a",
"text": "#0f172a",
"dark": {
"primary": "#4ade80"
}
}
}
}primary drives links, active nav states, buttons, and the topbar’s active tab underline. Every site gets a light/dark toggle automatically (top-right of the topbar) — styles.colors.dark only needs to specify the values that actually differ in dark mode; anything left unset falls back to the light-mode value.
Background color isn’t set here — see styles.background below, the single field for that.
styles.logo and styles.favicon
favicon is a single path. logo accepts either one path used for both color modes, or a { light?, dark?, label? } object — each image shown only while that mode is active:
{ "styles": { "logo": "/logo.svg" } }{
"styles": {
"logo": {
"light": "/logo-light.svg",
"dark": "/logo-dark.svg",
"label": "My Docs"
}
}
}Both forms are root-relative paths (/logo.svg, not a relative path or a filename alone), and they always match your project’s actual folder structure — whatever you’d type to get from your content directory to the file, that’s what goes here:
my-docs/
├── writedocs.json
└── logo.svg → "styles.logo": "/logo.svg"Any folder works the same way — there’s no dedicated assets folder to create, public/ included:
my-docs/
├── writedocs.json
└── assets/
└── logo.svg → "styles.logo": "/assets/logo.svg"Write the path that matches where the file actually is, and it resolves — no folder name is special. If you’re coming from Astro or a similar tool and already have a public/ folder, it works too, including its own convention of dropping the public segment (public/logo.svg → /logo.svg) — but that’s for familiarity only, never something you need to add or think about specifically.
On the object form, light and dark are both optional — set just one if you only have an image for one mode, or omit both entirely and set only label to override the topbar’s fallback text without adding any logo image:
{ "styles": { "logo": { "label": "My Docs" } } }The topbar shows only the logo image by default — no site name text next to it, since a real logo asset is usually already a wordmark. label (only available on the object form) opts back into showing text next to the image, for a symbol-only mark that needs a name alongside it, or stands alone with no image at all as shown above. With no styles.logo set at all, the topbar falls back to name.
styles.codeblocks
The Shiki theme name used for fenced code blocks and the API playground’s request/response snippets, in light and dark mode:
{
"styles": {
"codeblocks": {
"light": "min-light",
"dark": "dracula"
}
}
}Both default to github-light/github-dark if omitted. Any theme name from shiki.style/themes works — this is a built-in theme name, not a custom theme file. Leave either key unset to keep its default while overriding only the other.
styles.codeblocks.langAlias
Maps a fenced (```) code block’s own language tag to whichever Shiki grammar actually highlights it, for a language tag that either doesn’t tokenize well under its own grammar, or that you’d rather write under a shorter/friendlier tag than Shiki’s own bundled id:
{
"styles": {
"codeblocks": {
"langAlias": {
"groovy": "java"
}
}
}
}Merged with (not replacing) one built-in default — mdx is aliased to jsx out of the box, since Shiki’s own mdx grammar tokenizes an entire fence as one plain run with no tag/attribute distinction, while jsx actually highlights the JSX-heavy <Callout>/<Card>-style snippets this site’s own docs use ```mdx for.
styles.navbar
The topbar’s own background color, in light and dark mode — independent of styles.background (the page’s). Each side (light/dark) is either a plain color string, or an object with an optional accent override:
{
"styles": {
"navbar": {
"light": "#f8fafc",
"dark": "#020617"
}
}
}Both default to the page background if omitted — an unconfigured navbar blends into the page exactly as before. Leave either key unset to keep that side matching the page while overriding only the other. The mobile menu’s slide-out panel shares this same color, since its header row is designed to read as a continuation of the real topbar.
The moment either side is configured at all — plain string or object, doesn’t matter — every text/icon color drawn on top of it (the brand name, product/version switchers, tab labels, topbar links, the search box, the theme toggle) switches from styles.colors.text to an automatically-computed black or white, picked by contrast against that background. There’s no field to set this yourself: it’s always correct, including for the common way to accidentally break it — a brand-colored navbar (the same value as styles.colors.primary, or close to it), where styles.colors.text alone would otherwise read as near-invisible near-black-on-near-primary text.
accent is the one thing that is still a real design choice — the active-tab fill and the hover underline inside the navbar specifically (both of which otherwise use styles.colors.primary, same as everywhere else on the page):
{
"styles": {
"navbar": {
"light": { "background": "#6366f1", "accent": "#f59e0b" },
"dark": { "background": "#6366f1", "accent": "#f59e0b" }
}
}
}Leaving accent unset keeps the ordinary brand-colored highlight (styles.colors.primary) exactly as before — set it only when a navbar needs its own accent independent of that. Either way, the text painted on top of the active tab’s own fill is also chosen automatically by contrast, for the same reason the navbar’s plain text color is: getting an active-tab’s text color wrong is a legibility bug, not a legitimate design choice to expose.
styles.background
The single field for any background color, plus an optional image layered on top of it:
{
"styles": {
"background": {
"colors": {
"light": "#f8fafc",
"dark": "#0f172a"
},
"images": {
"light": "/images/light-background.png",
"dark": "/images/dark-background.png"
}
}
}
}Both colors and images are optional, and each side (light/dark) is independently optional too — leave a key unset to keep that side’s default while overriding only the other. images paths resolve the same way styles.logo/favicon do (see above) — the literal path from your content directory to the file, no matter which folder it’s in.
colors drives every flat, opaque surface site-wide — dropdowns, modals, kbd chips, the footer, and the topbar (unless styles.navbar overrides it) all use it, same as if it were still the single styles.colors.background field this repo used to have before the two were merged into one. images shows through the main content, table-of-contents, and sidebar columns — the topbar and footer always paint their own opaque copy of colors over themselves, specifically so a background image sits behind your content and navigation, not behind the chrome that frames them.
See docs.json-examples/00-kitchen-sink/ in this repo for a full working example of styles.navbar and styles.background together.
styles.fonts
Every writedocs site uses Inter by default, genuinely loaded as a web font — not just referenced as a name a reader might happen to already have installed. Set styles.fonts to use a different font instead, for your whole site or separately for headings and body text.
Google Fonts
Set family to any Google Fonts name and it loads automatically — no source needed:
{
"styles": {
"fonts": {
"family": "Space Grotesk"
}
}
}Headings and body text separately
Override just headings, just body text, or both — whichever isn’t overridden falls back to the top-level family (or Inter, if you haven’t set one):
{
"styles": {
"fonts": {
"family": "Inter",
"heading": {
"family": "Playfair Display",
"weight": 700
}
}
}
}Local or externally-hosted fonts
Set source (and format) to use a font file instead of a Google Font — either a path to a file in your own project (resolves the same way styles.logo/styles.background.images do — see above), or a full URL to a font hosted elsewhere:
{
"styles": {
"fonts": {
"family": "InterDisplay",
"source": "/fonts/InterDisplay-Regular.woff2",
"format": "woff2",
"weight": 400
}
}
}{
"styles": {
"fonts": {
"family": "Hubot Sans",
"source": "https://mintlify-assets.b-cdn.net/fonts/Hubot-Sans.woff2",
"format": "woff2",
"weight": 400
}
}
}A Google Fonts name, or the name you’re giving a source font.
e.g. 400, 700 — narrows which Google Fonts weight loads, or labels a source font’s own weight.
Omit to auto-load family from Google Fonts. Set to use a local or externally-hosted font file instead.
Required together with source.
Same four fields, applied to headings only. Falls back to the fields above when unset.
Same four fields, applied to body text only. Falls back to the fields above when unset.
weight controls both which font file loads and how bold it actually renders — set heading.weight/body.weight (or the top-level weight, which both fall back to) and it’s applied as font-weight on your headings/body text too, not just used to pick a file. Leave weight unset entirely and headings/body text keep the plain browser-default weight (bold headings, normal body text) exactly as before you configured styles.fonts at all.
See docs.json-examples/00-kitchen-sink/ for a working family + heading override.