MKT UI

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:

TokenWhat it controls
--brandEyebrows, icon accents, links inside sections, the brand surface
--brand-foregroundText and icons sitting on --brand
--brand-subtleTinted backgrounds — icon chips, the brand section surface
--brand-borderBorders that should read as branded rather than neutral
app/globals.css
: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:

app/globals.css
[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.

SurfaceUse it for
defaultMost sections
mutedSeparating two adjacent sections without a divider
brandOne or two moments per page, usually a CTA
invertedStrongest emphasis. Once per page at most
<Section surface="muted">
  <Container>…</Container>
</Section>

Type scale

TokenDefaultUsed by
--text-display-lg3.5remHero headline on wide screens
--text-display2.75remHero headline, large section headings
--text-title2remSection headings
--text-lead1.1875remLead 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

TokenDefaultUsed by
--container-mkt80remStandard page width
--container-mkt-narrow56remCentered hero and CTA copy
--container-mkt-prose44remArticle 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.

On this page