Skip to content
DocPensieve
0.2.0

Themes

Two stylings

theme: {
  framework: 'tailwind',
  darkMode: 'class',
},

tailwind compiles on demand only the classes actually used in the produced pages. custom does without it entirely: a stylesheet written in the package styles the site, with no styling dependency.

The templates are the same in both cases. Switching values requires touching no page.

The principle: slots, not classes

The templates hard-code no class. They ask the theme for the class of each slot — the header, the menu, a navigation link — and the theme answers.

The twenty-four slots are listed in the reference.

That is what lets a utility styling and a classic styling share the same HTML: one answers dp-nav-link, the other a handful of utilities. The template itself does not change.

Components follow the same rule. When the theme does not answer, they fall back on a dp-* class that their stylesheet styles from the --dp-* tokens: they therefore follow the active palette without knowing anything about it.

Changing the colours

The fifteen tokens are listed in the reference. They are redefined from the configuration:

theme: {
  framework: 'tailwind',
  tokens: {
    '--dp-accent': 'oklch(55% 0.2 250)',
    '--dp-radius': '0.75rem',
  },
},
TokenWhat it sets
--dp-bg, --dp-bg-softBackgrounds
--dp-text, --dp-text-softText
--dp-border, --dp-ruleBorders and rules
--dp-accent, --dp-accent-softAccent colour
--dp-radiusCorner rounding
--dp-font, --dp-font-monoFont families
--dp-content-widthReading width. none by default: the content fills its column
--dp-sidebar-width, --dp-toc-widthSide columns

A redefined token propagates everywhere: components included, without any of them having to know.

Adding CSS

theme: {
  framework: 'tailwind',
  css: '.dp-article h2 { letter-spacing: -0.01em; }',
},

The content of css is appended to the produced stylesheet.

Colour scheme

theme.darkMode decides which palette the reader gets. 'class', the default, follows the reader's system; 'dark' or 'light' keeps one whatever the system — the build sets it as a class on <html>.

The header also carries a button that switches between light and dark, and remembers the reader's choice from page to page — a few hundred bytes of inline script, the only one content pages carry. theme.toggle: false removes it. Without JavaScript, the button does not show.

The dark palette is a set of tokens, the same under both themes. To change it, redefine them in the theme/ folder, for both ways of being dark — the system's scheme, and the class that darkMode: 'dark' sets:

@media (prefers-color-scheme: dark) {
  :root:not(.light) {
    --dp-bg: #060814;
  }
}

:root.dark {
  --dp-bg: #060814;
}

The dark: utilities of your pages follow the same rule: they apply under the system's dark scheme, and always once darkMode: 'dark' is set.

<div className="bg-white dark:bg-slate-900">…</div>

Layer order

Under the tailwind theme, the stylesheet declares its layers in this order:

@layer theme, base, components, utilities;

Component rules live in components, below the utilities. A className set at use therefore always wins, whatever the place of the rule in the file:

<Card className="border-0 shadow-none">…</Card>

Without this layer, a component rule written after a utility would beat it at equal specificity, and the author's className would be ignored without a word.

The custom theme has no utilities: a className there names classes of your own. Write them in the theme/ folder, at the root of the project: every .css file in it is appended to the stylesheet, outside any layer, so they come before the component rules. Under this theme, init starts the folder with theme/custom.css.

The stylesheet is compiled last

A utility theme only emits the rules of the classes actually used: it therefore needs the rendered pages before it can compile. The build first writes every page, collecting the classes along the way, then compiles the stylesheet.

That is also why the slot classes are available without waiting for that compilation: the templates need them to be rendered. The two things are kept apart for this reason alone.