Skip to main content

Theme and override the layer

Change design tokens or replace a focused Nuxt page, layout, or shell component without copying the layer.

Change tokens first. Replace a component only when the existing structure cannot express the result you need.

Select a built-in palette

Choose neutral surfaces and the primary action color independently in app config:

app/app.config.ts
export default defineAppConfig({
  ginkoDocs: {
    theme: {
      neutral: "mauve",
      primary: "violet",
    },
  },
});

Use primary: 'neutral' when primary controls should follow the neutral palette. Palette selection changes shadcn semantic tokens; status colors and landing-page brand accents remain independent.

Define custom palettes

Set either palette to custom, then define its palette variables in the consumer theme CSS. A custom neutral is a complete ramp:

app/app.config.ts
export default defineAppConfig({
  ginkoDocs: {
    theme: { neutral: "custom", primary: "custom" },
  },
});
app/assets/css/theme.css
html[data-neutral="custom"] {
  --theme-neutral-50: oklch(0.985 0.004 250);
  --theme-neutral-100: oklch(0.965 0.008 250);
  --theme-neutral-200: oklch(0.92 0.012 250);
  --theme-neutral-300: oklch(0.87 0.018 250);
  --theme-neutral-400: oklch(0.71 0.025 250);
  --theme-neutral-500: oklch(0.55 0.03 250);
  --theme-neutral-600: oklch(0.45 0.03 250);
  --theme-neutral-700: oklch(0.37 0.028 250);
  --theme-neutral-800: oklch(0.28 0.024 250);
  --theme-neutral-900: oklch(0.21 0.02 250);
  --theme-neutral-950: oklch(0.14 0.012 250);
}

A custom primary defines explicit colors, foregrounds, and focus rings for both color modes:

app/assets/css/theme.css
html[data-primary="custom"] {
  --theme-primary-light: oklch(0.52 0.2 255);
  --theme-primary-light-foreground: oklch(0.985 0.004 255);
  --theme-primary-light-ring: oklch(0.62 0.17 255);
  --theme-primary-dark: oklch(0.72 0.14 255);
  --theme-primary-dark-foreground: oklch(0.18 0.025 255);
  --theme-primary-dark-ring: oklch(0.72 0.14 255);
}

Define the foregrounds instead of assuming white text; light colors such as yellow or lime need a dark foreground. Missing custom variables fall back individually to the Zinc and neutral defaults.

Load theme CSS after the layer

Import consumer theme CSS from a Nuxt plugin. The plugin import is added after the layer styles without replacing Nuxt's merged css array.

app/plugins/theme.ts
import "~/assets/css/theme.css";

export default defineNuxtPlugin(() => {});

Override the semantic variables used by the shell and content components:

app/assets/css/theme.css
:root {
  --font-body: Inter, system-ui, sans-serif;
  --font-heading: Inter, system-ui, sans-serif;
  --radius: 0.5rem;

  --background: oklch(0.99 0.004 255);
  --foreground: oklch(0.19 0.025 255);
  --primary: oklch(0.51 0.19 258);
  --primary-foreground: oklch(0.99 0.004 255);
  --muted: oklch(0.96 0.01 255);
  --muted-foreground: oklch(0.48 0.03 255);
  --border: oklch(0.9 0.015 255);
  --ring: oklch(0.62 0.16 258);
}

.dark {
  --background: oklch(0.16 0.02 255);
  --foreground: oklch(0.96 0.008 255);
  --primary: oklch(0.73 0.14 258);
  --primary-foreground: oklch(0.15 0.02 255);
  --muted: oklch(0.23 0.025 255);
  --muted-foreground: oklch(0.72 0.025 255);
  --border: oklch(1 0 0 / 12%);
  --ring: oklch(0.73 0.14 258);
}

Keep foreground and background pairs readable in both color modes. Add the chosen web fonts to the app before referencing them in a token.

Replace one shell component

Nuxt's layer override rules let the consumer replace a stable shell component with a component of the same name. Ginko Docs exposes these focused seams:

  • SiteHeader, SiteFooter, SiteBanner, and SiteLogoMark;
  • SiteInteractionLayer and DocsSidebar;
  • SiteLocaleSwitcher when the complete language-control UI needs a custom implementation.

For example, create app/components/SiteFooter.vue to replace only the footer:

app/components/SiteFooter.vue
<template>
  <footer class="border-t border-border px-6 py-8 text-sm text-muted-foreground">
    <p>© {{ new Date().getFullYear() }} Acme</p>
  </footer>
</template>

Configure light and dark logo paths in app.config.ts when only the mark changes; a component override is unnecessary for that case.

Replace a page or layout

Create the same Nuxt path in the consumer to replace a layer page or layout:

  • app
    • layouts
      • docs.vuedocumentation shell
    • pages
      • index.vuelanding page

An app/pages/index.vue file takes ownership of the landing page. An app/layouts/docs.vue file takes ownership of the documentation shell. A layout replacement must preserve accessible landmarks, the skip target, responsive navigation, and a place for <slot />.

Do not copy the complete layer into the application. A copied tree stops receiving fixes and makes it unclear whether configuration, tokens, or consumer code owns a visual decision.