Duvlify
GitHubGet started
The frameworkTheming
The framework

Theming

Accent, font, and radius are one line each in docs.config.ts, and the neutral palette derives from a single background colour.

Brand-level settings live in the theme block of src/docs.config.ts. ThemeTokens.astro turns them into CSS custom properties, and astro.config.ts hands the same font config to Astro’s fonts API. Changing the accent or the font is therefore a single edit, and no stylesheet needs touching.

src/docs.config.ts
export const theme = {
  font: { sans: 'Inter', sansWeights: ['400 800'], mono: 'ui-monospace, …' },
  accent: { base: '#16866a', strong: '#0f5e4a', strongDark: '#68b09e', contrast: '#ffffff' },
  headerPrimary: { background: '', foreground: '' },
  radius: { lg: '16px', md: '12px', sm: '8px', xs: '6px', smoothing: 'subtle' },
} as const;

The accent has four values, not one

This section explains a detail that often causes confusion.

FieldRole
baseFills and tints
strongText, borders, and active states, in light mode
strongDarkThe same role in dark mode, where strong would be too dark
contrastText placed on top of a solid base fill

strong exists because base usually cannot clear a 4.5:1 contrast ratio against the accent tints used behind badges, method pills, and changelog labels. It can only clear that ratio against the page background. Those tints are the accent at 12 to 24% over the background. A plausible-looking accent often fails contrast specifically at the tint.

contrast has its own risk. A mid-tone accent is the worst case. If you lighten base much past a 500-weight, neither white nor a dark shade of the same hue clears 4.5:1 on it, because the fill then sits halfway between the two. If nothing works, move base instead of searching for a text colour.

An unrelated call-to-action colour

The topbar’s filled primary button takes the accent colour by default. Set either field of headerPrimary to break that link. A brand’s call-to-action colour is sometimes fixed and unrelated to the site’s accent, and it should not move whenever the accent does. Many brands use a black call-to-action colour.

Leaving both '' keeps the default, accent.base over accent.contrast.

Changing the page background

--bg in src/styles/tokens.css is the one colour a site most often wants to move, for example to a light grey canvas that matches a marketing site. Change only this value. The raised surfaces follow it automatically.

--surface-2 (hover) and --surface-active (the current item) are not fixed colours. Each is --bg mixed a measured distance toward --text in OKLab. This holds the same perceptual separation from whatever background you set, in both colour modes. The percentages differ by mode because perceived contrast differs by mode: dark mode needs roughly twice the lightness step to read the same as light mode.

Fonts

TypeScript
font: {
  sans: 'Inter',
  sansWeights: ['400 800'],
  sansVariable: '--font-brand',
  mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
}

The site uses sans for all interface and prose text. Astro downloads the family at build time and emits the @font-face rules and preloads. This avoids a render-blocking round trip to a font CDN, and it stops any third party from learning your readers’ IP addresses.

A single entry like '400 800' requests that weight range from a variable font, which is one file instead of five. mono is a plain CSS stack: code is set in whatever font the reader’s system already has. This is both faster and what most developers prefer.

Radius, and continuous corners

Radii step down as elements nest: shells use lg, then wells use md, then controls use sm or xs.

smoothing turns on iOS-style continuous corners. The curve blends smoothly into each straight edge, instead of the abrupt transition of an ordinary circular radius. This uses native CSS, the corner-shape property, applied once and globally in shell.css. Nothing here is a JavaScript or mask polyfill. A browser that does not yet know the property simply keeps the plain radius, because an unrecognised CSS property is inert and does not break anything.

ValueResult
noneOrdinary round corners
subtleA light continuous curve, what this site uses
standardA clearly continuous curve
iosThe spec’s own squircle keyword, the strongest curve

Where the rest lives

The neutral palette and the component styling are in src/styles/tokens.css and components.css. The site designs both colour modes directly, rather than deriving one mode from the other by inversion.

If you find yourself editing a stylesheet to change a brand value, treat that as a gap in the theme block, not something to work around.

Use these docs with your AI tools

An AI agent can read this documentation directly. You do not need an account or an API key. Everything here is public and read-only.

Query these docs via MCP

Recommended

Add this server to Claude, Claude Code, Cursor, Mistral, or any tool that supports MCP. Your agent can then search Duvlify documentation and read it in full, instead of answering from memory.

https://duvlify.dev/mcp
  • searchFind the passages that answer a question.
  • fetchRead one page in full, as Markdown.
  • list_pagesSee every page in this documentation.

Query these docs over HTTP

The same tools also work as plain web requests. Use this for scripts, or for any tool that does not support MCP. There is one endpoint per tool. Arguments go in the query string, and the answer comes back as JSON.

https://duvlify.dev/api/docs/search?query=custom+domain

Read the OpenAPI description. It is built from the same definitions as the tools, so it always matches what the endpoints do.

Read these docs as Markdown

Add .md to any page URL to get its Markdown source. You can also send the headerAccept: text/markdown to the page URL itself.

To read the whole documentation in one file, open llms-full.txt. For a short index of every page, open llms.txt.