Theming and overrides
Colours, fonts and sizes of the docs layer through tokens, the two colour presets, light and dark, your own parts at the override points, and your own pages in the docs frame.
Change tokens first. Replace a part only when the tokens cannot give you the result. Everything on this page is public and stays stable between releases.
Pick a preset
theme in app.config.ts picks one of two colour presets:
// app/app.config.ts
export default defineAppConfig({
docs: { theme: 'green' },
})| Preset | Colours |
|---|---|
default | Emerald on zinc |
green | Green on slate |
A preset sets defaults only. Every token your site sets wins over both.
Colours and fonts
The layer is plain CSS. Colours, radius and fonts are shadcn-style tokens, the same ones @lupinum/ginko-components reads:
--background, --foreground, --muted, --muted-foreground, --accent, --border, --primary, --primary-foreground, --card, --popover, --ring, --radius, --font-sans and --font-mono.
The layer's defaults sit in @layer theme with zero specificity. So every value your site sets wins, unlayered or through Tailwind 4's @theme:
/* app/assets/site.css */
:root {
--primary: light-dark(#2f6f4f, #7cc59d);
--primary-foreground: light-dark(#fff, #0c1f15);
--font-sans: 'Inter', system-ui, sans-serif;
}- Set
--primary-foregroundtogether with--primary. A light colour such as yellow needs dark text; check the contrast in both modes. - Your site loads its own font before a token names it. The layer brings Inter and JetBrains Mono.
- On a Tailwind 4 site that sets no
--font-sansand--font-mono, Tailwind's fonts win. - Code blocks have tokens of their own (
--ginko-code-bgand more), see Components.
Light and dark
The defaults are light-dark(light, dark) pairs. Without a choice the site follows the system. The switch in the header stores the reader's choice in localStorage (key docs-color-mode) and sets data-theme="light" or data-theme="dark" on <html>. A small script in the head applies it before the first paint, so nothing flickers.
A .dark class on <html>, for example from @nuxtjs/color-mode, works too. When your site sets its own values for both modes, write them as light-dark() pairs as well.
Sizes
The layout sizes are tokens too:
| Token | Default | What it sets |
|---|---|---|
--docs-max | 90rem | Width of the docs, gutters included |
--docs-gutter | 1rem | Space between the window edge and the docs |
--docs-top | 0px (standalone: the header) | Height of what sticks above the docs |
--docs-sidebar | 17rem | Width of the navigation |
--docs-toc | 13.5rem | Width of "On this page" beside the text (from 80rem window width) |
--docs-measure | 38rem | Reading width of running text |
--docs-h1 | clamp(1.75rem, …, 2.25rem) | Size of page titles |
--docs-header | 4rem | Height of the standalone header |
Inside an existing site, --docs-max and --docs-top line the docs up with the rest of the site; more on Docs sites.
Override points
The layer ships two empty components. Your site replaces them with its own file of the same name in app/components/:
DocsPageEnd(propdoc, the page's entry): after the text of every page, before "Edit this page" and the pager. For a support box or a call to action.DocsSidebarEnd: below the navigation, in the sidebar and in the phone menu. For a few links or a contact line.
<!-- app/components/DocsPageEnd.vue -->
<script setup lang="ts">
defineProps<{ doc: { id: string } }>()
</script>
<template>
<aside class="support">Questions? <a href="mailto:help@example.com">Write to us</a>.</aside>
</template>Do not replace other Docs* components: they are internal and can change in any release. The layer's CSS classes are no contract either. Style through the tokens above.
Own pages and own layout
- A page wins. When your site has a page at the same address, for example
app/pages/index.vue, the layer adds none of its own there. - Your own page in the docs frame.
<DocsShell>is the docs body: navigation, search and the phone bar. The default slot takes the page, the slotbarcontrols for the phone bar:
<!-- app/pages/help/index.vue; the docs live at /help (docsCollection({ locales, base: '/help' })) -->
<script setup lang="ts">
const docs = useGinko('docs')
</script>
<template>
<DocsShell>
<h1>How can we help?</h1>
<ul>
<li v-for="item in docs.items" :key="item.id"><NuxtLink :to="item.url">{{ item.data.title }}</NuxtLink></li>
</ul>
</DocsShell>
</template>- Your own layout. With its own
app/layouts/default.vue, your site keeps its header and footer, and the docs sit inside.<DocsShell>brings the search, above the navigation. You can also put the search, the languages and the light/dark switch into your own header:<DocsSearch />,<DocsLanguage />,<DocsThemeToggle />. Withdocs: { search: false }inapp.config.tsthe layer then leaves out its own search. - Texts. Every text of the layer is a message under
docs.*, and your site's messages win key by key. A help center words the search for itself ini18n/en.json, for example:{ "docs": { "searchPlaceholder": "Search help…" } }.
Do not copy the layer into your site. A copy no longer gets fixes, and it becomes unclear whether tokens, configuration or your code makes a decision.