The theme is not a dependency you configure. It is a set of files the layer
owns, built on [shadcn-vue](https://www.shadcn-vue.com) and Tailwind 4, and the
reason it is shaped that way decides what overriding one costs you.

Changing something is [Override the theme](/guides/override-the-theme). This page
is why the seams sit where they do.

## Components are copied in, not imported

shadcn-vue is not a component library you install. Its CLI writes the source of a
component into your project, and from then on the file is yours. The layer holds
its primitives in `app/components/ui/`, and `components.json` points the CLI at
that alias, so adding one is a single command.

Two consequences follow, and they run in opposite directions:

- **You can change anything, without a fork.** A component you want to look
  different is a file you edit — there is no upstream to fight, no theme API to
  find a gap in.
- **Nothing updates itself.** An upstream fix to a primitive reaches you when you
  re-run the CLI for it, not when a dependency bumps. The layer carries that cost
  for its own primitives; a component you override in your project, you carry.

That trade is the whole reason for the third level in the override guide: the
smallest change that does the job is the right one, because a component you
replace is a component you now maintain.

::callout{type="info" title="Where the decision came from"}
Why shadcn-vue rather than a ready-made documentation theme is recorded in
[ADR 0008](/adr/0008-build-the-theme-on-owned-shadcn-vue-components).
::

## One token layer, two stylesheets

The palette is CSS custom properties in `oklch`, defined once on `:root` and
redefined on `.dark`. Every component reads a token; none of them holds a colour.
That is what makes a one-line override possible — you redefine `--primary` in
your own stylesheet and every surface that uses it moves.

Two files carry it, and they are not interchangeable:

| File                          | Holds                                                                    |
| :---------------------------- | :----------------------------------------------------------------------- |
| `app/assets/css/duxt.css`     | The palette, the Tailwind entry, the layer's own preset — the file to read |
| `app/assets/css/typeset.css`  | shadcn's typeset, vendored verbatim — the file **not** to edit            |

`typeset.css` has no registry item behind it, so the CLI can neither fetch nor
update it. Upgrading means downloading the file again, which is why local changes
belong in `duxt.css`'s preset instead: an edit made in `typeset.css` is an edit
the next upgrade silently discards.

## Dark mode is a class, not a media query

`@custom-variant dark (&:is(.dark *))` — the theme switches on a `dark` class,
and `@nuxtjs/color-mode` sets exactly that class because the layer configures it
with `classSuffix: ''`.

This is the detail that catches people overriding a colour. A token defined only
under `@media (prefers-color-scheme: dark)` is never read, and a token defined
once on `:root` is used in both modes — which means it is wrong in one of them.
Redefine in both blocks, or accept that one mode keeps the layer's value.

## The palette deviates from shadcn's default, deliberately

`--muted-foreground` is darker here than the shadcn neutral set ships it. That
token carries the page descriptions, the table of contents and the breadcrumb,
and at shadcn's value it measures below the 4.5:1 that WCAG AA asks for on the
surfaces it actually lands on.

Nothing in a browser-based check catches this: an accessibility run over the
built pages has no computed colour to measure. The layer therefore measures the
tokens themselves in `tests/contrast.test.ts`, pairing each foreground with the
backgrounds it is really used on. Override a foreground token and you take that
guarantee over — the test reads the layer's file, not yours.

## Tailwind has to be told where the layer is

`@source '../../../app'` sits near the top of `duxt.css` and is not decoration.
Tailwind scans for class names from the **build root**, which is the consuming
project's directory. The layer lives outside it, in `node_modules`, so without
that line every utility used by a layer component would be absent from the
generated CSS — a site that renders with no styles at all.

The path is relative to the stylesheet, so it holds wherever the package is
installed.

## Where this leaves you

- A colour, a radius, a font: redefine the token in your own stylesheet.
- A layout that is wrong for your site: shadow the component by name.
- A primitive the layer does not carry: add it with the CLI.

Each step down that list costs more maintenance than the one above it. The
[override guide](/guides/override-the-theme) walks all three, and
[Brand your site](/guides/brand-your-site) covers the part that is identity
rather than theme — the name, the mark and the icons.