Customize
Themes
Install a ready-made look with one command. A theme is one JSON file, so there is nothing else to wire up.
A theme is one JSON file. It carries colors, spacing, the shape of the progress counter, the arrow, the spotlight and the scrim — everything about how a tour looks, and nothing about what it does.
Because it is only data, installing one writes one file into your project. There is no package to add at runtime, no build step, and nothing to keep in sync with your framework.
Install
Section titled “Install”-
Add a theme. Pick one from the gallery below:
Terminal window npx @docentjs/cli theme add ledgerThis writes
docent-theme.json, after checking it. Use--outto put it somewhere else, anddocent theme listto see every theme. -
Hand it to the renderer. One line, wherever you already set up Docent:
import { createTour } from '@docentjs/dom'import theme from './docent-theme.json'createTour(tour, { renderer: { template: theme } })import { DocentProvider } from '@docentjs/react'import theme from './docent-theme.json'<DocentProvider renderer={{ template: theme }}><App /></DocentProvider>import { provideDocentDefaults } from '@docentjs/vue'import theme from './docent-theme.json'provideDocentDefaults({ renderer: { template: theme } })import { useDocent } from '@docentjs/svelte'import theme from './docent-theme.json'const docent = useDocent({ tours, renderer: { template: theme } }) -
That is it. Every tour in the app now uses the theme.
From anywhere
Section titled “From anywhere”A theme is just a URL away, so anyone can publish one. The CLI checks it before writing anything, and refuses a file that is not a valid theme:
npx @docentjs/cli theme add https://example.com/my-theme.jsonThe same check is available in code as validateTheme(theme) from
@docentjs/dom/validate, for themes you load at runtime.
Prefer not to use the CLI? Copy the JSON from any card below into your project instead. It is the same file.
Make it yours
Section titled “Make it yours”Open the file and change it. Every field is documented in theming and arrows and spotlight; the ones a theme uses most:
| Field | What it does |
|---|---|
theme |
colors, radius, width, padding, font, shadow, scrim |
eyebrow |
a small line above every title. {tour} becomes the tour’s name |
progress |
meter, count, ticks, dots or none |
arrow |
caret, none, or one of the ten drawn connectors |
spotlight |
padding, radius, shape and ring around the target |
overlay |
dim, blur, vignette or none |
mobile |
on phones: layout (auto, float, dock) and card (stories, compact, classic); a tour’s own mobile wins |
css |
anything the fields above do not cover, scoped to the popover |
A tour can override any of it, so one tour can go its own way without a second theme:
{ "options": { "template": "ledger", "progress": "dots", "eyebrow": "New in 2.4" } }The gallery
Section titled “The gallery”Press Preview to run a real tour with that theme. These are live — the card is the library rendering, not a picture of it.
npx @docentjs/cli theme add bloombloom.json
{
"name": "Bloom",
"theme": {
"background": "oklch(99% 0.008 330)",
"foreground": "oklch(26% 0.05 330)",
"muted": "oklch(58% 0.04 330)",
"accent": "oklch(58% 0.21 350)",
"accentForeground": "oklch(99% 0.008 350)",
"connector": "oklch(58% 0.21 350)",
"ring": "oklch(78% 0.16 350)",
"radius": 20,
"width": 336,
"padding": "20px 22px 16px",
"shadow": "0 2px 4px oklch(40% 0.12 330 / 0.1), 0 28px 60px -28px oklch(40% 0.14 330 / 0.6)",
"duration": 260
},
"progress": "dots",
"arrow": "squiggle",
"spotlight": {
"padding": 10,
"radius": 16,
"ring": "pulse",
"animate": true
},
"overlay": {
"color": "oklch(30% 0.09 330)",
"opacity": 0.38
},
"css": ".title { font-size: 19px; letter-spacing: -0.015em; } .button { border-radius: 999px; padding-inline: 14px; }"
}npx @docentjs/cli theme add broadsheetbroadsheet.json
{
"name": "Broadsheet",
"theme": {
"background": "oklch(98.6% 0.006 85)",
"foreground": "oklch(21% 0.012 60)",
"muted": "oklch(52% 0.014 60)",
"accent": "oklch(45% 0.14 28)",
"accentForeground": "oklch(98% 0.008 85)",
"connector": "oklch(45% 0.14 28)",
"radius": 3,
"width": 360,
"padding": "18px 20px 16px",
"duration": 190
},
"eyebrow": "{tour}",
"progress": "count",
"spotlight": {
"padding": 8,
"radius": 3,
"ring": "hairline"
},
"overlay": {
"color": "oklch(24% 0.03 60)",
"opacity": 0.46
},
"css": ".title { font-family: ui-serif, Georgia, 'Times New Roman', serif; font-size: 21px; letter-spacing: -0.01em; } .eyebrow { letter-spacing: 0.14em; } .footer { margin-top: 20px; padding-top: 12px; border-top: 1px solid color-mix(in oklch, var(--docent-fg) 14%, transparent); }"
}npx @docentjs/cli theme add carboncarbon.json
{
"name": "Carbon",
"theme": {
"background": "oklch(13% 0.01 285)",
"foreground": "oklch(99% 0.002 285)",
"muted": "oklch(88% 0.008 285)",
"accent": "oklch(90% 0.18 100)",
"accentForeground": "oklch(13% 0.01 285)",
"connector": "oklch(90% 0.18 100)",
"ring": "oklch(90% 0.18 100)",
"radius": 4,
"width": 340,
"padding": "18px",
"shadow": "none"
},
"progress": "ticks",
"spotlight": {
"padding": 6,
"radius": 2,
"ring": "solid"
},
"overlay": {
"opacity": 0.8
},
"css": ".popover { box-shadow: 0 0 0 2px var(--docent-accent); } .marks i { height: 3px; }"
}npx @docentjs/cli theme add consoleconsole.json
{
"name": "Console",
"theme": {
"background": "oklch(24.5% 0.016 60)",
"foreground": "oklch(94% 0.008 60)",
"muted": "oklch(72% 0.012 60)",
"accent": "oklch(74% 0.165 58)",
"accentForeground": "oklch(19% 0.04 60)",
"connector": "oklch(74% 0.165 58)",
"ring": "oklch(74% 0.165 58)",
"radius": 8,
"width": 344,
"padding": "16px 16px 12px",
"shadow": "0 24px 56px -24px oklch(8% 0.02 60 / 0.95)",
"duration": 200
},
"eyebrow": "{tour}",
"progress": "count",
"count": "{current2}/{total2}",
"spotlight": {
"ring": "hairline",
"padding": 8,
"radius": 6,
"animate": true
},
"overlay": {
"color": "oklch(12% 0.014 60)",
"opacity": 0.66
},
"css": ".count { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.02em; } .button { border-radius: 6px; }"
}npx @docentjs/cli theme add ledgerledger.json
{
"name": "Ledger",
"theme": {
"background": "oklch(99.3% 0.005 95)",
"foreground": "oklch(24% 0.014 95)",
"muted": "oklch(58% 0.011 95)",
"accent": "oklch(42% 0.082 155)",
"accentForeground": "oklch(98% 0.012 155)",
"connector": "oklch(42% 0.082 155)",
"ring": "oklch(98% 0.01 95)",
"radius": 12,
"width": 352,
"padding": "18px 18px 14px",
"shadow": "0 1px 2px oklch(24% 0.014 95 / 0.08), 0 26px 52px -26px oklch(24% 0.014 95 / 0.55)",
"duration": 220
},
"eyebrow": "{tour}",
"progress": "ticks",
"count": "{current2} / {total2}",
"spotlight": {
"padding": 10,
"radius": 10,
"animate": true
},
"overlay": {
"color": "oklch(32% 0.03 95)",
"opacity": 0.44
}
}npx @docentjs/cli theme add nocturnenocturne.json
{
"name": "Nocturne",
"theme": {
"background": "oklch(23% 0.013 265)",
"foreground": "oklch(95% 0.004 265)",
"muted": "oklch(74% 0.008 265)",
"accent": "oklch(95% 0.004 265)",
"accentForeground": "oklch(14.5% 0.008 265)",
"connector": "oklch(68% 0.13 48)",
"ring": "oklch(68% 0.13 48)",
"radius": 10,
"width": 340,
"padding": "16px 16px 12px",
"shadow": "0 26px 60px -26px oklch(6% 0.01 265 / 0.95)",
"duration": 200
},
"eyebrow": "{tour}",
"progress": "dots",
"arrow": "curve",
"spotlight": {
"padding": 8,
"radius": 9,
"ring": "glow",
"animate": true
},
"overlay": {
"color": "oklch(10% 0.01 265)",
"opacity": 0.68
}
}npx @docentjs/cli theme add quietquiet.json
{
"name": "Quiet",
"theme": {
"background": "oklch(99.4% 0.003 285)",
"foreground": "oklch(23% 0.018 285)",
"muted": "oklch(56% 0.014 285)",
"accent": "oklch(26% 0.02 285)",
"radius": 8,
"width": 300,
"padding": "14px 16px 12px",
"shadow": "0 1px 2px oklch(23% 0.02 285 / 0.08), 0 6px 16px -6px oklch(23% 0.02 285 / 0.14)"
},
"progress": "none",
"arrow": "curve",
"spotlight": {
"ring": "glow",
"padding": 6,
"radius": 8
},
"overlay": {
"style": "none"
},
"css": ".title { font-size: 16px; }"
}Writing your own
Section titled “Writing your own”Start from the closest theme, change the tokens, and keep going until the fields
run out. If you reach something no field covers, css is the next step, and
slots after that — but a theme that needs
code is no longer something you can publish, share or let a tool edit, so it is
worth staying in JSON as long as you can.