Foundation

Colours, type and elevation are CSS custom properties, declared once for light and once for dark. Redeclaring them is how you theme the kit; overriding a class stays available for the cases that need it.

The name

margo is Latin for the edge of something — a border, a rim, the margin of a page. It is the word English took "margin" from, and it is the whole design brief of the kit in one term: what separates things here is a line and the space around it.

So the components are drawn rather than filled. A Card is a border and a radius, not a slab of colour; a Button at rest is an outline; a Table is rows separated by rules. There is one border width in the entire system — 1.5px — and one border colour, which is why the interface reads as consistent before any component is involved.

css
--margo-border-width-2: 1.5px;
--margo-color-border: #cfcfcf;

Everything else is restraint. Elevation exists but stays a hint rather than a drop shadow; the accent colour appears on what is active or focused and nowhere else; motion is short and reversible. The intent is a clean, modern surface that never raises its voice — decoration is what you add on purpose, not what the kit hands you by default.

Colours

Colours come in pairs: a surface and what goes on top of it. main is the page, neutral is what sits above it, and each has its on- counterpart for text and icons. Naming them by role rather than by hue is what lets the dark theme swap the values without a single component knowing.

token
--margo-color-mainpage surface, the background everything sits on
--margo-color-on-maintext and icons over the page surface
--margo-color-neutralraised surface: pure white in light, pure black in dark
--margo-color-on-neutraltext over the raised surface
--margo-color-primaryaccent, used by glows, gradients and active states
--margo-color-primary-darkenthe darker end of every primary gradient
--margo-color-secondarymuted fill, as in chips
--margo-color-mediumsecondary text, the resting state of icons
--margo-color-borderevery border of the kit

Scales

Type, weight, elevation and the single border width are tokens too. There is one border width on purpose: two would need a rule for when to use which, and the kit prefers to spend that decision elsewhere.

token
--margo-font-family-primaryNunito, the reading typeface
--margo-font-family-secondaryBebas Neue, the display typeface
--margo-font-size-*mc, xs, sm, base, md, lg, 2lg, xl, 2xl … 9xl
--margo-font-weight-*thin through black, mapped to the numeric weights
--margo-border-width-21.5px, the single border width of the kit
--margo-shadow-cardelevation of Card
--margo-shadow-buttonelevation of Button
--margo-shadow-itemelevation of Item, on hover, focus and active
--margo-shadow-chipelevation of Chip
--margo-code-*the snippet palette: surface, bar, and one colour per token type
--margo-layer-durationhow long layered surfaces take to arrive and leave
--margo-layer-easingthe curve they travel on: fast to start, slow to settle

Overriding a colour

Redeclare the variable after importing the kit. No build step, no plugin, no theme object: the components read the variable at paint time, so the new value reaches them and the utilities at once. This is the way to change something everywhere — a brand accent, a surface, the border colour.

css
/* after @import "margo-ui/css" */
:root {
  --margo-color-primary: #4f8cff;
  --margo-color-primary-darken: #2f6ad9;
}

.dark {
  --margo-color-primary: #7aa9ff;
  --margo-color-primary-darken: #4f8cff;
}

Override both themes when you touch a colour that exists in both. Forgetting the .dark block is the usual way a brand accent ends up unreadable at night.

Overriding in one place only

Because they are inherited custom properties, the same override works scoped to a subtree: put it on a class or a data attribute and everything inside adopts it, including the components of the kit.

css
.margo-invoice {
  --margo-color-primary: #e5a950;
  --margo-color-border: #e0d3bb;
}

How tokens reach the utilities

A second layer maps the --margo- variables onto Tailwind's theme. That is what makes bg-main, text-on-main, border-border and shadow-card exist as classes at all.

css
@theme {
  --color-main: var(--margo-color-main);
  --color-on-main: var(--margo-color-on-main);
  --color-primary: var(--margo-color-primary);
  --shadow-card: var(--margo-shadow-card);
  --text-md: var(--margo-font-size-md);
}

The indirection is deliberate. Overriding a --margo- variable changes both the kit and your own utility classes; overriding the Tailwind variable directly would change only the utilities, and the two would drift apart.

When a token is the wrong tool

Tokens are for decisions that hold across the interface. A single component that has to look different in one place is not one of those: there, pass a className and let cn merge it over the defaults. The kit is built for that too — it is why every component takes a className and why the merge exists at all.

The distinction is worth keeping: a token changes the system, a class changes an instance. Reaching for a token to fix one screen makes every other screen shift with it; reaching for a class to change a colour used in twenty places means twenty places to remember.

Adding tokens of your own

The same mechanism is open to your project: declare your own @theme block after the kit and you get utilities on the same footing as the built-in ones. This site does exactly that for the navbar height and the page offsets.

css
/* your own theme file, imported after the kit */
@theme {
  --spacing-navbar: 3.5rem;
  --spacing-page-offset: 1.5rem;
  --z-index-navbar: 50;
}