Domphy

Customization

Component slots

Every major layout region can be replaced with a custom Domphy element via themeConfig.slots. Each slot receives the full LayoutContext and must return a DomphyElement | null.

import { defineConfig, type LayoutContext, type DomphyElement } from "@domphy/press"
import { themeColor, themeSpacing } from "@domphy/theme"
import { toolbar } from "@domphy/ui"

export default defineConfig({
  themeConfig: {
    slots: {
      header: (ctx: LayoutContext): DomphyElement => ({
        header: [
          { a: ctx.config.title, href: ctx.config.base },
          {
            nav: ctx.config.themeConfig.nav.map(item => ({
              a: item.text, href: item.link,
            })),
            $: [toolbar({ gap: 4 })],
          },
        ],
        style: {
          display: "flex", alignItems: "center", justifyContent: "space-between",
          padding: `0 ${themeSpacing(6)}`,
          height: themeSpacing(14),
          background: themeColor(null, "shift-1"),
          borderBottom: `1px solid ${themeColor(null, "shift-3")}`,
        },
      }),
    },
  },
})

Available slots

SlotReplaces
headerThe entire header bar (logo, nav, actions)
sidebarThe sidebar navigation panel
asideThe TOC aside panel
prevNextThe prev/next pagination row
docFooterEdit link + last-updated row
footerThe page footer bar

Returning null from a slot omits the region entirely.

LayoutContext

Every slot function receives:

interface LayoutContext {
  route: string                          // current route, e.g. "/guide/intro"
  title: string                          // page title from frontmatter or h1
  body: DomphyElement[]                  // parsed markdown body elements
  toc: TocEntry[]                        // table of contents entries
  frontmatter: Record<string, unknown>   // raw frontmatter values
  config: SiteConfig                     // full site config
  lastUpdated?: string                   // ISO date from git, if enabled
  readingTime?: number                   // estimated minutes
  filePath?: string                      // relative path from srcDir
}

Frontmatter controls

Add these to the frontmatter of any .md file to control per-page behavior.

KeyTypeDescription
titlestringPage title (overrides the first # h1)
descriptionstringMeta description for this page
badgestring | { text, type }Badge shown next to the page title
sidebarfalseHide the sidebar and expand the content to full width
asidefalseHide the TOC aside panel
drafttrueExclude the page from the build output
heroHeroConfigEnable the home page hero section
featuresFeatureConfig[]Enable the home page features grid

Example

---
title: "API Reference"
badge: { text: "Beta", type: "warning" }
sidebar: false
---

Full-width content without sidebar navigation.

CSS theming

All layout element styles come from Domphy's generateCSS() (inline style:{} objects on elements). The pressCSS() function covers only the global reset and markdown-rendered HTML content (code blocks, custom blocks, etc.).

Theme tokens

Use CSS custom properties from @domphy/theme in your own styles. The same tokens drive the built-in layout:

import { themeColor, themeSpacing } from "@domphy/theme"

const brand = themeColor(null, "shift-9", "primary")  // var(--primary-9)
const border = themeColor(null, "shift-3")             // var(--neutral-3)
const space4 = themeSpacing(4)                         // "1em"

These resolve via CSS variables, so dark mode works automatically.

Injecting extra CSS

Add a <style> tag via config.head:

import { themeColor } from "@domphy/theme"

export default defineConfig({
  head: [
    `<style>
      /* custom scrollbar */
      ::-webkit-scrollbar { width: 6px; }
      ::-webkit-scrollbar-thumb { background: ${themeColor(null, "shift-3")}; border-radius: 3px; }
    </style>`,
  ],
})

Overriding with a custom slot

For layout-level changes, replace the slot and use inline style:{} with Domphy theme tokens — this is the approach that keeps everything in generateCSS():

slots: {
  footer: (ctx) => ({
    footer: `© ${new Date().getFullYear()} My Company`,
    style: {
      padding: `${themeSpacing(4)} ${themeSpacing(8)}`,
      background: themeColor(null, "shift-1"),
      borderTop: `1px solid ${themeColor(null, "shift-3")}`,
      fontSize: "13px", color: themeColor(null, "shift-6"),
      textAlign: "center",
    },
  }),
},

Logo variants

Pass an object with light and dark image paths to themeConfig.logo to show different logos per theme:

themeConfig: {
  logo: {
    light: "/logo-light.svg",
    dark:  "/logo-dark.svg",
  },
}

Press automatically hides the appropriate variant using [data-theme] attribute switching.

Announcement bar

Show a dismissible banner above the header:

themeConfig: {
  announcementBar: {
    id: "v2-release",       // unique ID stored in localStorage to remember dismissal
    text: "🎉 v2.0 is out! <a href='/blog/v2'>Read the post →</a>",
    dismissible: true,      // default: true
  },
}

Set id so the user only sees it once per release. Omit id to always show on page load.