Jant

Theming

Jant offers three ways to customize the look, from coarse to fine:

  1. Built-in color themes
  2. Built-in font themes
  3. Custom CSS

Start with Settings > Color Theme and Settings > Font Theme. When those aren't enough, write overrides in Settings > Custom CSS.

All built-in themes include both light and dark palettes. Custom CSS layers on top of the selected theme — so the best approach is usually to treat a built-in theme as your starting point rather than rewriting everything from scratch.

Color variables

Color variables come in two layers:

  • Core palette (--primary, --background, etc.) controls the overall feel; changing one affects the whole site.
  • Site-specific colors (--site-*) derive from the core palette by default, letting you fine-tune local areas without disturbing the global palette — for example, recoloring links without touching button colors.

Most color variables come in pairs: a background color and its matching foreground (text) color.

Core palette

Variable What it controls
--background Page background
--foreground Main text color
--primary Functional brand color (buttons, selected UI)
--primary-foreground Text on primary-colored elements
--secondary Secondary buttons, badges
--secondary-foreground Text on secondary elements
--muted Subtle backgrounds (hover states, badges)
--muted-foreground Secondary text (dates, captions, placeholders)
--accent Hover/focus backgrounds (menus, nav items)
--accent-foreground Text on accent backgrounds
--card Card background
--card-foreground Text on cards
--popover Dropdown / popover background
--popover-foreground Text in popovers
--destructive Danger actions (delete buttons, error states)
--success Success indicators
--border Borders and dividers
--input Input field borders
--ring Focus ring color

Site-specific colors

These derive from the core palette by default. Built-in themes set --site-accent independently; if you only override the core palette by hand, --site-accent falls back to --primary.

Variable Default What it controls
--site-accent var(--primary) Editorial accent: quote marks, Thread connector dots, the tint of link cards, hover and focus rings
--site-page-bg var(--background) Page background, and the surfaces that sit on it
--site-subtle-bg var(--accent) Soft fill for navigation hover, cards in the feed, and code blocks
--site-text-primary var(--foreground) Primary text
--site-text-secondary var(--muted-foreground) Secondary / caption text
--site-divider var(--border) Content dividers
--site-threadline var(--border) Thread connection lines
--site-reading-body Derived from --site-text-primary Body text on a post's own page. Built-in themes set it, so changing --foreground alone doesn't reach it
--site-reading-caption Derived from the text colors Captions and footnotes on a post's page
--site-content-link inherit Link text in posts
--site-content-link-hover var(--site-text-primary) Link text on hover
--site-content-link-underline Derived from --site-text-secondary Link underline
--site-footnote-text var(--site-reading-caption) Footnote text in the wide-screen rail
--site-footnote-marker Derived from --site-text-secondary Footnote numbers in the rail
--site-search-mark-bg Built-in yellow Search result highlight background
--site-search-mark-color Built-in dark text Search result highlight text

Example: custom primary and site accent

:root {
  --primary: oklch(0.48 0.08 255);
  --primary-foreground: oklch(0.98 0 0);
  --site-accent: oklch(0.56 0.06 240);
}

:root[data-theme-mode="dark"] {
  --primary: oklch(0.79 0.06 255);
  --primary-foreground: oklch(0.19 0.01 255);
  --site-accent: oklch(0.74 0.07 240);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme-mode="light"]) {
    --primary: oklch(0.79 0.06 255);
    --primary-foreground: oklch(0.19 0.01 255);
    --site-accent: oklch(0.74 0.07 240);
  }
}

The two dark rules are explained under Dark mode.

Example: colored thread connectors

:root {
  --site-threadline: oklch(0.8 0.05 250);
}

Typography variables

Variable Default What it controls
--font-body System sans-serif Body text, inputs
--font-heading Editorial serif stack Post titles and h1–h3
--font-site-title Editorial serif stack Site logo (header)
--font-ui System sans-serif Buttons, navigation, labels, badges (not affected by font themes)
--font-serif System serif + Noto fallback Serif accent text
--font-blockquote inherit Blockquote font family; defaults to the body font
--font-mono System monospace Code blocks
--type-body-size Content body text size Post body text; footnote references and code scale from it
--type-footnote-ref 75% of content body text Inline footnote reference size
--fw-regular 400 Body text
--fw-medium 500 Labels, active nav
--fw-semibold 600 Headings, buttons

When you pick a font theme in Settings > Font Theme, --font-heading, --font-body, and a few related font weights switch with it. --font-ui is intentionally left out — buttons, navigation, and other interface text always stay system sans-serif for legibility. You can still override any variable in Custom CSS for further tuning.

To use external font sources like Google Fonts, load the font files first via code injection, then point the variables at the new fonts in Custom CSS.

Example: lighter font weights

:root {
  --fw-medium: 400;
  --fw-semibold: 500;
}

Layout variables

Variable Default What it controls
--layout-body-max-width 1088px Widest the page frame gets: header, content, and footer
--site-feed-rhythm 4.4rem Space between posts in a list, and above the first one
--layout-sidenote-width 50% Wide-screen footnote rail width
--layout-sidenote-gap 10% Gap between prose and footnote rail

Example: wider content area

On a wide screen the reading column is a fixed share of the frame, so a wider frame gives it more room. Below 1024px the column keeps a 35rem measure whatever the frame.

:root {
  --layout-body-max-width: 1280px;
}

Sidenotes and indented blocks

Footnotes use semantic source HTML: superscript noteref links in the body and native ordered endnotes after the authored content. On detail pages and public timeline lists, Jant uses each post's actual container—not only the viewport—to decide whether there is room for a Tufte-style right rail. A small progressive client enhancer positions the existing endnote list when a container is at least 56rem wide and the notes fit without excessive overflow. Every timeline item is measured independently. Narrow or dense containers, print, no-JavaScript clients, and older browsers keep bottom endnotes.

Because the enhancer reads each noteref's real position instead of anchoring a float to its containing block, padding on blockquotes or other prose blocks no longer needs a compensating anchor offset. Rail numbers share the note text's size and baseline. Customize its proportions and semantic colors through theme tokens:

:root {
  --layout-sidenote-width: 42%;
  --layout-sidenote-gap: 8%;
  --type-footnote-ref: calc(var(--type-body-size) * 0.75);
  --site-footnote-text: var(--site-reading-caption);
  --site-footnote-marker: var(--site-text-secondary);
}

The rail visually suppresses backlink arrows because each note is already next to its first reference. The backlink remains in the semantic HTML for bottom endnotes and assistive technology, and becomes visible if a keyboard user focuses it.

Theme selectors should prefer .footnote-endnotes, .footnote-list, .footnote, .footnote-ref, .footnote-backlinks, and .footnote-backref, the arrow link back to a reference. Fragment IDs are opaque and may change in any release; never style or parse their compact hash.

Media and avatars

Variable Default What it controls
--media-radius 0.5rem Image / video corner radius
--avatar-size 28px Header avatar size
--avatar-radius 50% Avatar corner radius

Data attributes

You can target specific pages or elements with these data attributes when writing selectors:

Attribute Where it appears Values
data-theme <html> Active color theme id (tufte, linen, frost, …)
data-theme-mode <html> auto, light, dark
data-page <body> home, post, search, archive, collection, collections, featured, subscribe
data-post <article> Present on every post
data-format <article> note, link, quote
data-post-slug <article> Post slug (handy for debugging and per-post styling)
data-post-pinned <article> Present on pinned posts
data-post-featured <article> Present on Featured posts
data-feed Feed container Wraps the post list
data-authenticated <body> Present when logged in
data-post-end After a post's page Empty slot after the post, where code injection mounts comments

Inside each post there are four more markers: data-post-body (the body, or a quote's commentary), data-post-quote (a quote's quoted text), data-post-meta (the date and the collections), and data-post-media (the attachments: images, video, audio, documents, and text files). Together they let you style any one section of a post on its own.

Posts in the feed, on a post's page, and in a collection carry every attribute and marker above. A search result carries the <article> attributes but none of the markers inside: it shows a snippet instead of the post. A tile in the archive's grid view carries data-post and data-format only.

Example: dividers only on the home page

[data-page="home"] [data-post] {
  border-bottom: 1px solid var(--border);
  padding-bottom: 1.5rem;
}

Example: style by format

[data-post-quote] {
  font-family: var(--font-serif);
  font-style: italic;
}

[data-format="link"] {
  border-left: 3px solid var(--site-accent);
  padding-left: 1rem;
}

Example: highlight pinned posts

[data-post-pinned] {
  background: var(--muted);
  border-radius: 8px;
  padding: 1rem;
}

Dark mode

A site follows the visitor's system preference (light / dark) unless it is set to always light or always dark; data-theme-mode on <html> says which. A page is dark in two ways, so a dark value takes two rules:

:root {
  --site-threadline: oklch(0.8 0.05 250);
}

/* The site is set to dark */
:root[data-theme-mode="dark"] {
  --site-threadline: oklch(0.45 0.05 250);
}

/* The system is dark, and the site isn't set to light */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme-mode="light"]) {
    --site-threadline: oklch(0.45 0.05 250);
  }
}

Write the selectors as shown. The built-in theme uses the same ones, and custom CSS comes after it, so yours win. A plain :root inside the media query is less specific and loses to the theme's dark values.

Tips

  • Override variables first, write selectors second. Jant keeps the variables, data attributes, and classes on this page stable where it can, and a release that changes one says so in its release notes; see Compatibility. Jant's stylesheets define many more custom properties and class names; those are internal and can change in any release without notice.
  • Custom CSS comes after the built-in theme and uses the same selectors, so it overrides every variable the theme sets: :root for light values, the two rules under Dark mode for dark ones.
  • oklch() is convenient for color tuning. A common pattern: keep --primary saturated and stable for buttons; let --site-accent carry a softer tone for quote marks and Thread connectors. Links take --site-content-link.
  • Test in both light and dark. A color set only in :root also applies in dark mode wherever neither the theme nor Jant sets a dark value for it.

What's next