Skip to content

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.

  1. Add a theme. Pick one from the gallery below:

    Terminal window
    npx @docentjs/cli theme add ledger

    This writes docent-theme.json, after checking it. Use --out to put it somewhere else, and docent theme list to see every theme.

  2. 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 } })
  3. That is it. Every tour in the app now uses the theme.

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:

Terminal window
npx @docentjs/cli theme add https://example.com/my-theme.json

The 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.

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 nineteen 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" } }

Press Preview to run a real tour with that theme. These are live — the card is the library rendering, not a picture of it.

Style Themes
Quiet and modern quiet, slate, ledger
Classy atelier (ivory, Didone serif, a framed card), velvet (plum and gold), broadsheet
Glassmorphism frost (light frosted glass), aurora (dark glass with a violet-to-ice sheen)
Neumorphism pebble (soft light clay), graphite (dark clay with a mint accent)
Brutalism poster (thick ink borders, hard shadows, loud yellow), concrete (raw, monospace, all square)
Dark and vivid nocturne, console, neon, carbon (high contrast)
Playful bloom
Ateliercount progress · eyebrow · double arrow
npx @docentjs/cli theme add atelier
atelier.json
{
  "name": "Atelier",
  "theme": {
    "background": "oklch(97.6% 0.012 85)",
    "foreground": "oklch(22% 0.015 250)",
    "muted": "oklch(50% 0.015 250)",
    "accent": "oklch(28% 0.05 255)",
    "accentForeground": "oklch(97.6% 0.012 85)",
    "connector": "oklch(97% 0.012 85)",
    "ring": "oklch(97% 0.012 85)",
    "radius": 2,
    "width": 360,
    "padding": "24px 24px 18px",
    "shadow": "0 1px 2px oklch(22% 0.02 60 / 0.08), 0 30px 60px -30px oklch(22% 0.03 60 / 0.55)",
    "duration": 240
  },
  "eyebrow": "{tour}",
  "progress": "count",
  "count": "{current} of {total}",
  "arrow": "double",
  "spotlight": {
    "padding": 10,
    "radius": 2,
    "animate": true
  },
  "overlay": {
    "color": "oklch(26% 0.02 60)",
    "opacity": 0.48
  },
  "css": ".popover, .popover.sheet { box-shadow: 0 0 0 1px oklch(22% 0.015 250 / 0.12), inset 0 0 0 6px var(--docent-bg), inset 0 0 0 7px oklch(22% 0.015 250 / 0.14), var(--docent-shadow); } .title { font-family: Didot, 'Bodoni 72', 'Iowan Old Style', ui-serif, Georgia, serif; font-size: 22px; font-weight: 400; line-height: 1.2; letter-spacing: -0.01em; } .eyebrow, .count { font-family: 'Iowan Old Style', ui-serif, Georgia, serif; font-style: italic; text-transform: none; letter-spacing: 0.01em; font-size: 13px; } .footer { margin-top: 22px; } .button { border-radius: 0; font-size: 11.5px; letter-spacing: 0.1em; text-transform: uppercase; } .button.primary { padding-inline: 18px; }"
}
Aurorameter progress · s-curve arrow
npx @docentjs/cli theme add aurora
aurora.json
{
  "name": "Aurora",
  "theme": {
    "background": "oklch(24% 0.035 285 / 0.58)",
    "foreground": "oklch(97% 0.008 285)",
    "muted": "oklch(80% 0.03 285)",
    "accent": "oklch(80% 0.12 300)",
    "accentForeground": "oklch(20% 0.04 285)",
    "connector": "oklch(87% 0.09 225)",
    "ring": "oklch(87% 0.09 225)",
    "radius": 18,
    "width": 344,
    "padding": "20px 20px 16px",
    "shadow": "0 30px 70px -28px oklch(8% 0.05 285 / 0.9)",
    "duration": 260
  },
  "progress": "meter",
  "arrow": "s-curve",
  "spotlight": {
    "padding": 8,
    "radius": 12,
    "ring": "glow",
    "animate": true
  },
  "overlay": {
    "style": "blur",
    "blur": 6,
    "color": "oklch(16% 0.06 280)",
    "opacity": 0.5
  },
  "css": ".popover { background-image: linear-gradient(140deg, oklch(70% 0.14 300 / 0.18), transparent 45%, oklch(72% 0.12 220 / 0.14)); -webkit-backdrop-filter: blur(26px) saturate(1.7); backdrop-filter: blur(26px) saturate(1.7); } .popover, .popover.sheet { box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.16), 0 0 0 1px oklch(100% 0 0 / 0.12), var(--docent-shadow); } .popover .arrow { border-color: oklch(100% 0 0 / 0.12) !important; -webkit-backdrop-filter: blur(26px); backdrop-filter: blur(26px); } .button { border-radius: 999px; } .button.primary, .button.primary:hover { padding-inline: 16px; background: linear-gradient(120deg, oklch(80% 0.12 300), oklch(85% 0.1 220)); } .button.primary:hover { filter: brightness(1.07); } .meter { width: 40px; background: linear-gradient(90deg, oklch(80% 0.12 300), oklch(85% 0.1 220)) 0 0 / calc(var(--docent-step) / var(--docent-steps) * 100%) 100% no-repeat, oklch(100% 0 0 / 0.14); }"
}
Bloomdots progress · squiggle arrow
npx @docentjs/cli theme add bloom
bloom.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; }"
}
Broadsheetcount progress · eyebrow
npx @docentjs/cli theme add broadsheet
broadsheet.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); }"
}
Carbonticks progress
npx @docentjs/cli theme add carbon
carbon.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; }"
}
Concretecount progress · eyebrow · zigzag arrow
npx @docentjs/cli theme add concrete
concrete.json
{
  "name": "Concrete",
  "theme": {
    "background": "oklch(90% 0.004 95)",
    "foreground": "oklch(14% 0 0)",
    "muted": "oklch(38% 0.004 95)",
    "accent": "oklch(14% 0 0)",
    "accentForeground": "oklch(90% 0.004 95)",
    "connector": "oklch(96% 0 0)",
    "ring": "oklch(96% 0 0)",
    "radius": 0,
    "width": 360,
    "padding": "16px 18px 14px",
    "shadow": "none",
    "font": "ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace",
    "duration": 120
  },
  "eyebrow": "// {tour}",
  "progress": "count",
  "count": "[{current2}/{total2}]",
  "arrow": "zigzag",
  "spotlight": {
    "padding": 6,
    "radius": 0,
    "shape": "rect",
    "ring": "dashed"
  },
  "overlay": {
    "color": "oklch(14% 0 0)",
    "opacity": 0.56
  },
  "css": ".popover, .popover.sheet { box-shadow: 0 0 0 2px var(--docent-fg); } .popover .arrow { border-color: var(--docent-fg) !important; border-width: 2px !important; border-radius: 0 !important; } .title { font-size: 15px; font-weight: 700; line-height: 1.35; text-transform: uppercase; letter-spacing: 0.02em; } .eyebrow { text-transform: none; letter-spacing: 0; font-size: 11px; } .body { font-size: 13px; color: var(--docent-fg); } .footer { margin-top: 16px; padding-top: 12px; border-top: 2px solid var(--docent-fg); } .count { color: var(--docent-fg); letter-spacing: 0; } .button, .popover.stories [part~='button-next'] { border-radius: 0; } .button { box-shadow: inset 0 0 0 2px var(--docent-fg); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; } .button:hover, [part~='button-skip']:hover { background: var(--docent-fg); color: var(--docent-bg); } [part~='button-skip'] { box-shadow: none; } .button.primary { box-shadow: none; } .button.primary:hover { background: oklch(34% 0 0); } .close { border-radius: 0; color: var(--docent-fg); } .close:hover { background: var(--docent-fg); color: var(--docent-bg); }"
}
Consolecount progress · eyebrow
npx @docentjs/cli theme add console
console.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; }"
}
Frostdots progress · arc arrow
npx @docentjs/cli theme add frost
frost.json
{
  "name": "Frost",
  "theme": {
    "background": "oklch(99% 0.004 250 / 0.62)",
    "foreground": "oklch(22% 0.02 255)",
    "muted": "oklch(45% 0.02 255)",
    "accent": "oklch(22% 0.02 255)",
    "accentForeground": "oklch(99% 0.004 250)",
    "connector": "oklch(22% 0.02 255)",
    "ring": "oklch(99% 0.004 250)",
    "radius": 20,
    "width": 340,
    "padding": "20px 20px 16px",
    "shadow": "0 1px 1px oklch(30% 0.03 255 / 0.06), 0 24px 56px -20px oklch(30% 0.05 255 / 0.42)",
    "duration": 240
  },
  "progress": "dots",
  "arrow": "arc",
  "spotlight": {
    "padding": 8,
    "radius": 14,
    "ring": "glow",
    "animate": true
  },
  "overlay": {
    "style": "blur",
    "blur": 3,
    "color": "oklch(42% 0.05 255)",
    "opacity": 0.3
  },
  "css": ".popover { -webkit-backdrop-filter: blur(22px) saturate(1.8); backdrop-filter: blur(22px) saturate(1.8); } .popover, .popover.sheet { box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.75), 0 0 0 1px oklch(100% 0 0 / 0.6), var(--docent-shadow); } .popover .arrow { border-color: oklch(100% 0 0 / 0.6) !important; -webkit-backdrop-filter: blur(22px); backdrop-filter: blur(22px); } .button { border-radius: 999px; } .button:hover { background: oklch(100% 0 0 / 0.5); } .button.primary { padding-inline: 16px; } .marks i { background: oklch(22% 0.02 255 / 0.16); }"
}
Graphiteticks progress · eyebrow · marker arrow
npx @docentjs/cli theme add graphite
graphite.json
{
  "name": "Graphite",
  "theme": {
    "background": "oklch(27% 0.008 260)",
    "foreground": "oklch(93% 0.006 260)",
    "muted": "oklch(71% 0.01 260)",
    "accent": "oklch(82% 0.13 165)",
    "accentForeground": "oklch(22% 0.03 165)",
    "connector": "oklch(82% 0.13 165)",
    "ring": "oklch(82% 0.13 165)",
    "radius": 22,
    "width": 340,
    "padding": "22px 22px 18px",
    "shadow": "9px 9px 22px oklch(13% 0.008 260 / 0.85), -9px -9px 22px oklch(39% 0.01 260 / 0.5)",
    "duration": 240
  },
  "eyebrow": "{tour}",
  "progress": "ticks",
  "arrow": "marker",
  "spotlight": {
    "padding": 10,
    "radius": 14,
    "animate": true
  },
  "overlay": {
    "color": "oklch(27% 0.008 260)",
    "opacity": 0.84
  },
  "css": ".popover, .popover.sheet { box-shadow: var(--docent-shadow); } .popover .arrow { border-color: transparent !important; } .ring { box-shadow: 6px 6px 16px oklch(13% 0.008 260 / 0.8), -6px -6px 16px oklch(39% 0.01 260 / 0.5); } .eyebrow { color: var(--docent-accent); } .button { border-radius: 12px; } .button:hover { background: transparent; color: var(--docent-accent); } .button.primary { padding-inline: 16px; box-shadow: 4px 4px 10px oklch(13% 0.008 260 / 0.8), -4px -4px 10px oklch(39% 0.01 260 / 0.45); } .button.primary:hover { background: var(--docent-accent); color: var(--docent-accent-fg); } .button.primary:active { scale: 1; box-shadow: inset 3px 3px 7px oklch(50% 0.1 165 / 0.6), inset -3px -3px 7px oklch(92% 0.1 165 / 0.6); } .close { border-radius: 10px; box-shadow: 3px 3px 7px oklch(13% 0.008 260 / 0.7), -3px -3px 7px oklch(39% 0.01 260 / 0.45); } .close:hover { background: transparent; } .marks i { height: 4px; border-radius: 999px; background: oklch(20% 0.008 260); box-shadow: inset 1px 1px 1px oklch(13% 0.008 260); } .marks i[data-done] { background: var(--docent-accent); box-shadow: none; }"
}
Ledgerticks progress · eyebrow
npx @docentjs/cli theme add ledger
ledger.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
  }
}
Neonticks progress · eyebrow · glow arrow
npx @docentjs/cli theme add neon
neon.json
{
  "name": "Neon",
  "theme": {
    "background": "oklch(17% 0.025 285)",
    "foreground": "oklch(97% 0.01 285)",
    "muted": "oklch(75% 0.03 285)",
    "accent": "oklch(87% 0.15 190)",
    "accentForeground": "oklch(18% 0.03 220)",
    "connector": "oklch(72% 0.25 340)",
    "ring": "oklch(87% 0.15 190)",
    "radius": 14,
    "width": 340,
    "padding": "18px 18px 14px",
    "shadow": "0 0 0 1px oklch(87% 0.15 190 / 0.35), 0 0 32px -6px oklch(87% 0.15 190 / 0.35), 0 28px 60px -24px oklch(5% 0.02 285 / 0.95)",
    "duration": 220
  },
  "eyebrow": "{tour}",
  "progress": "ticks",
  "arrow": "glow",
  "spotlight": {
    "padding": 8,
    "radius": 10,
    "ring": "pulse",
    "animate": true
  },
  "overlay": {
    "color": "oklch(8% 0.03 285)",
    "opacity": 0.72
  },
  "css": ".popover, .popover.sheet { box-shadow: var(--docent-shadow); } .popover .arrow { border-color: oklch(87% 0.15 190 / 0.35) !important; } .eyebrow { color: oklch(76% 0.22 340); letter-spacing: 0.16em; } .title { text-shadow: 0 0 18px oklch(87% 0.15 190 / 0.35); } .button.primary { box-shadow: 0 0 0 1px oklch(87% 0.15 190 / 0.6), 0 0 18px -2px oklch(87% 0.15 190 / 0.7); } .marks i[data-done] { background: var(--docent-accent); box-shadow: 0 0 6px oklch(87% 0.15 190 / 0.8); }"
}
Nocturnedots progress · eyebrow · curve arrow
npx @docentjs/cli theme add nocturne
nocturne.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
  }
}
Pebbledots progress · coil arrow
npx @docentjs/cli theme add pebble
pebble.json
{
  "name": "Pebble",
  "theme": {
    "background": "oklch(93.5% 0.008 250)",
    "foreground": "oklch(30% 0.02 255)",
    "muted": "oklch(50% 0.018 255)",
    "accent": "oklch(56% 0.13 255)",
    "accentForeground": "oklch(98% 0.005 250)",
    "connector": "oklch(56% 0.13 255)",
    "ring": "oklch(56% 0.13 255)",
    "radius": 24,
    "width": 336,
    "padding": "22px 22px 18px",
    "shadow": "10px 10px 24px oklch(72% 0.02 255 / 0.7), -10px -10px 24px oklch(100% 0 0 / 0.9)",
    "duration": 260
  },
  "progress": "dots",
  "arrow": "coil",
  "spotlight": {
    "padding": 10,
    "radius": 16,
    "animate": true
  },
  "overlay": {
    "color": "oklch(93.5% 0.008 250)",
    "opacity": 0.76
  },
  "css": ".popover, .popover.sheet { box-shadow: var(--docent-shadow); } .popover .arrow { border-color: transparent !important; } .ring { box-shadow: 7px 7px 16px oklch(72% 0.02 255 / 0.7), -7px -7px 16px oklch(100% 0 0 / 0.95); } .button { border-radius: 999px; } .button:hover { background: transparent; color: var(--docent-accent); } .button.primary { padding-inline: 18px; box-shadow: 5px 5px 12px oklch(72% 0.02 255 / 0.75), -5px -5px 12px oklch(100% 0 0 / 0.9); } .button.primary:hover { background: var(--docent-accent); color: var(--docent-accent-fg); } .button.primary:active { scale: 1; box-shadow: inset 3px 3px 7px oklch(40% 0.12 255 / 0.55), inset -3px -3px 7px oklch(72% 0.12 255 / 0.5); } .close { border-radius: 999px; box-shadow: 3px 3px 7px oklch(72% 0.02 255 / 0.6), -3px -3px 7px oklch(100% 0 0 / 0.9); } .close:hover { background: transparent; } .marks i { background: transparent; box-shadow: inset 1px 1px 2px oklch(68% 0.02 255 / 0.8), inset -1px -1px 2px oklch(100% 0 0 / 0.9); } .marks i[data-done] { background: var(--docent-accent); box-shadow: none; }"
}
Postercount progress · eyebrow · block arrow
npx @docentjs/cli theme add poster
poster.json
{
  "name": "Poster",
  "theme": {
    "background": "oklch(98.5% 0.012 95)",
    "foreground": "oklch(16% 0 0)",
    "muted": "oklch(36% 0 0)",
    "accent": "oklch(89% 0.18 102)",
    "accentForeground": "oklch(16% 0 0)",
    "connector": "oklch(89% 0.18 102)",
    "ring": "oklch(89% 0.18 102)",
    "radius": 2,
    "width": 344,
    "padding": "20px 20px 18px",
    "shadow": "0 0 0 2.5px oklch(16% 0 0), 7px 7px 0 2.5px oklch(16% 0 0)",
    "duration": 140
  },
  "eyebrow": "{tour}",
  "progress": "count",
  "count": "{current2} / {total2}",
  "arrow": "block",
  "spotlight": {
    "padding": 6,
    "radius": 2,
    "shape": "rect",
    "ring": "solid"
  },
  "overlay": {
    "color": "oklch(16% 0 0)",
    "opacity": 0.42
  },
  "css": ".popover, .popover.sheet { box-shadow: var(--docent-shadow); } .popover .arrow { border-color: var(--docent-fg) !important; border-width: 2.5px !important; border-radius: 0 !important; } :host([data-ring='solid']) .ring { box-shadow: 0 0 0 3px currentColor; } .title { font-size: 20px; font-weight: 800; letter-spacing: -0.02em; } .eyebrow { align-self: flex-start; padding: 3px 6px; background: oklch(72% 0.2 350); color: var(--docent-fg); font-weight: 700; box-shadow: 0 0 0 2px var(--docent-fg); margin-bottom: 4px; } .body { color: var(--docent-fg); } .count { color: var(--docent-fg); font-weight: 700; } .button, .popover.stories [part~='button-next'] { border-radius: 0; font-weight: 700; } .button.primary, .button.primary:hover { background: var(--docent-accent); box-shadow: 0 0 0 2px var(--docent-fg), 3px 3px 0 2px var(--docent-fg); transition: translate 80ms ease-out, box-shadow 80ms ease-out; } .button.primary:hover { translate: -1px -1px; box-shadow: 0 0 0 2px var(--docent-fg), 4px 4px 0 2px var(--docent-fg); } .button.primary:active { scale: 1; translate: 3px 3px; box-shadow: 0 0 0 2px var(--docent-fg); } .close { border-radius: 0; color: var(--docent-fg); } .close:hover { background: var(--docent-accent); }"
}
Quietno progress · curve arrow · no scrim
npx @docentjs/cli theme add quiet
quiet.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; }"
}
Slatemeter progress · curve arrow
npx @docentjs/cli theme add slate
slate.json
{
  "name": "Slate",
  "theme": {
    "background": "oklch(100% 0 0)",
    "foreground": "oklch(21% 0.02 265)",
    "muted": "oklch(52% 0.02 265)",
    "accent": "oklch(54% 0.22 268)",
    "accentForeground": "oklch(99% 0.005 268)",
    "connector": "oklch(82% 0.1 268)",
    "ring": "oklch(82% 0.1 268)",
    "radius": 16,
    "width": 344,
    "padding": "18px 18px 14px",
    "shadow": "0 0 0 1px oklch(21% 0.02 265 / 0.07), 0 2px 4px -1px oklch(21% 0.02 265 / 0.06), 0 12px 24px -6px oklch(21% 0.02 265 / 0.12), 0 40px 80px -24px oklch(30% 0.12 268 / 0.35)",
    "duration": 220
  },
  "progress": "meter",
  "arrow": "curve",
  "spotlight": {
    "padding": 8,
    "radius": 12,
    "ring": "pulse",
    "animate": true
  },
  "overlay": {
    "color": "oklch(21% 0.03 265)",
    "opacity": 0.5
  },
  "css": ".popover, .popover.sheet { box-shadow: var(--docent-shadow); } .title { letter-spacing: -0.02em; } .button { border-radius: 10px; } .button.primary { background: linear-gradient(180deg, oklch(62% 0.2 268), var(--docent-accent)); box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.25), 0 1px 2px oklch(30% 0.15 268 / 0.4), 0 4px 12px -2px oklch(54% 0.22 268 / 0.45); } .button.primary:hover { background: linear-gradient(180deg, oklch(66% 0.2 268), oklch(57% 0.22 268)); } .meter { width: 44px; height: 4px; background: linear-gradient(var(--docent-accent), var(--docent-accent)) 0 0 / calc(var(--docent-step) / var(--docent-steps) * 100%) 100% no-repeat, oklch(21% 0.02 265 / 0.08); }"
}
Velvetticks progress · eyebrow · swoosh arrow
npx @docentjs/cli theme add velvet
velvet.json
{
  "name": "Velvet",
  "theme": {
    "background": "oklch(21% 0.035 345)",
    "foreground": "oklch(95% 0.012 80)",
    "muted": "oklch(76% 0.03 60)",
    "accent": "oklch(83% 0.1 82)",
    "accentForeground": "oklch(22% 0.035 345)",
    "connector": "oklch(83% 0.1 82)",
    "ring": "oklch(83% 0.1 82)",
    "radius": 6,
    "width": 352,
    "padding": "22px 24px 18px",
    "shadow": "0 30px 64px -28px oklch(8% 0.04 345 / 0.95)",
    "duration": 280
  },
  "eyebrow": "{tour}",
  "progress": "ticks",
  "arrow": "swoosh",
  "spotlight": {
    "padding": 10,
    "radius": 8,
    "ring": "glow",
    "animate": true
  },
  "overlay": {
    "color": "oklch(12% 0.03 345)",
    "opacity": 0.66
  },
  "css": ".popover, .popover.sheet { box-shadow: 0 0 0 1px oklch(83% 0.1 82 / 0.32), inset 0 0 0 5px var(--docent-bg), inset 0 0 0 6px oklch(83% 0.1 82 / 0.2), var(--docent-shadow); } .popover .arrow { border-color: oklch(83% 0.1 82 / 0.32) !important; } .title { font-family: 'Iowan Old Style', 'Palatino Linotype', Palatino, ui-serif, Georgia, serif; font-size: 22px; font-weight: 500; letter-spacing: -0.005em; } .eyebrow { color: var(--docent-accent); font-size: 10px; letter-spacing: 0.24em; } .body { color: oklch(87% 0.016 70); } .button { border-radius: 999px; letter-spacing: 0.02em; } .button.primary { padding-inline: 18px; } .marks i[data-done] { background: var(--docent-accent); }"
}

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.