Theming
Re-theme an entire website by changing four tokens.
MKT UI uses shadcn's token system and adds a small semantic layer on top. No component hard-codes a color, so re-theming is a stylesheet change rather than a find-and-replace through your components.
The brand tokens
These four are the ones you will actually change:
| Token | What it controls |
|---|---|
--brand | Eyebrows, icon accents, links inside sections, the brand surface |
--brand-foreground | Text and icons sitting on --brand |
--brand-subtle | Tinted backgrounds — icon chips, the brand section surface |
--brand-border | Borders that should read as branded rather than neutral |
:root {
--brand: oklch(0.512 0.144 244);
--brand-foreground: oklch(0.99 0 0);
--brand-subtle: oklch(0.966 0.019 244);
--brand-border: oklch(0.879 0.056 244);
}All four are oklch(lightness chroma hue). To move the brand to a different color,
keep the lightness and chroma and change only the hue — that preserves the contrast
relationships the palette was built with.
Check your contrast
--brand is used for text, and not only on white: eyebrows sit on --surface and
on --brand-subtle too. Check all three. The default rose clears 4.5:1 against
every one of them, and an earlier, brighter value that passed on white alone failed
on the tinted surfaces at 4.09:1 — which is exactly the kind of thing an automated
check catches and an eyeball does not. pnpm test:e2e runs axe-core over
representative pages.
How the examples do it
Each example website sets a data-theme attribute on a wrapper and overrides nothing
but the brand tokens:
[data-theme="home-service"] {
--brand: oklch(0.512 0.144 244); /* utility blue */
--brand-foreground: oklch(0.99 0 0);
--brand-subtle: oklch(0.966 0.019 244);
--brand-border: oklch(0.879 0.056 244);
}
[data-theme="consultant"] {
--brand: oklch(0.444 0.161 285); /* deep indigo */
/* … */
}<div data-theme="home-service">
<HomeServiceHome />
</div>Compare the home-service example with this site's own pages: same sections, same spacing, same type — different business.
Surfaces
Section takes a surface prop instead of a background class, which is what keeps
alternating bands consistent down a long page.
| Surface | Use it for |
|---|---|
default | Most sections |
muted | Separating two adjacent sections without a divider |
brand | One or two moments per page, usually a CTA |
inverted | Strongest emphasis. Once per page at most |
<Section surface="muted">
<Container>…</Container>
</Section>Type scale
| Token | Default | Used by |
|---|---|---|
--text-display-lg | 3.5rem | Hero headline on wide screens |
--text-display | 2.75rem | Hero headline, large section headings |
--text-title | 2rem | Section headings |
--text-lead | 1.1875rem | Lead paragraphs under a heading |
Each carries its own line height and letter spacing. Display sizes are set tight
(-0.032em) because large text looks loose at the tracking that suits body copy.
Content widths
| Token | Default | Used by |
|---|---|---|
--container-mkt | 80rem | Standard page width |
--container-mkt-narrow | 56rem | Centered hero and CTA copy |
--container-mkt-prose | 44rem | Article body text |
<Container width="narrow">…</Container>Radius and density
--radius comes from shadcn and drives every corner in the library through the
--radius-sm … --radius-4xl scale. Lower it to about 0.3rem for a sharper,
more corporate feel; raise it to 0.9rem for something softer.
Dark mode
Every token has a .dark counterpart, so nothing renders as light-on-light if you
enable dark mode. Full dark-mode design polish is not part of this first release —
the tokens exist so that adding it later does not mean revisiting every component.
Fonts
Sections use font-sans and font-heading. Point them at whatever you like:
@theme inline {
--font-sans: var(--font-your-body);
--font-heading: var(--font-your-display);
}Setting --font-heading to a display face and leaving --font-sans alone is usually
enough to make the library feel like a different design system.