KANTO DS v1.12.0
Customise

Theme

Accent

Font

Corners

Density

Text size

Reset to Kanto
Design

Token layer

GitHub Copy link

Every visual decision in Kanto resolves to a CSS custom property declared on :root. Components never hard-code a colour, a size or a duration.

Every visual decision in Kanto resolves to a CSS custom property declared on :root. Components never hard-code a colour, a size or a duration.

Custom properties inherit through shadow roots, which is what makes this work for web components: an app can restyle every Kanto element by redefining a token on :root, without piercing a single shadow boundary.

Files

FileContentsOptional
colors.cssDark palette, surfaces, text ramp, aliasesno
theme-light.cssLight remap of the same namesno
typography.cssFamilies and composite font shorthandsno
spacing.cssRadius, padding, gap, dimensions, motion, zno
responsive.cssBreakpoint overrides + reduced-motionno
fonts.css@font-face for Manrope / Mulish / SCPyes
base.cssGlobal element styles for the host applicationyes
index.cssImports the five non-optional files—

Usage

import 'kanto-ds/styles.css'; // fonts + tokens + base

or, to keep Kanto out of the host's global styles:

import 'kanto-ds/tokens/index.css'; // variables only

Theming

Dark is the canonical theme and is what :root carries.

<html data-theme="light"> <!-- forced light --> <html data-theme="auto"> <!-- follows the OS --> </html> </html>

Accent colour

Kanto ships violet. Seven presets are a data attribute away, on the page or on any container:

<html data-accent="blue"></html>

violet · blue · teal · green · orange · pink · slate

Any other colour goes through setAccent, which solves the variants for it:

import { setAccent } from 'kanto-ds'; setAccent('#e11d48'); // the page setAccent('#e11d48', panel); // one container setAccent(null); // back to the preset or the default

Whatever the colour, white text reads on a primary fill at 4.5:1 or more, and the accent as text reads at 4.5:1 or more on the surfaces, on its own tint and on the hover wash of a secondary button, in both themes. A bright colour — a yellow, a light orange — is darkened for it, as Kanto's own violet is. The neutral surfaces take a trace of the accent's hue; a grey accent gives plain greys.

accentPalette(color) returns the values without applying them — to write them into a stylesheet at build time, for instance. The inputs it sets are --accent-base, --accent-hover, --accent-text-dark, --accent-text-light, --accent-wash, --neutral-hue and --neutral-chroma; components never read them, only the tokens do.

A red or a green accent sits close to the danger and success colours; Kanto does not stop you, but a primary button and a destructive one will look alike.

Appearance

Besides the accent, four settings change how Kanto looks. Each is a data attribute, on the page or on any container:

<html data-font="inter" data-radius="round" data-density="compact" data-text-size="large"></html>
AttributeValues
data-fontkanto · system · inter · plex · geist
data-radiussharp · default · round
data-densitycompact · default · comfortable
data-text-sizesmall · default · large
data-themedark · light · auto

Or all at once, with the accent:

import { setAppearance, readAppearance } from 'kanto-ds'; setAppearance({ theme: 'auto', accent: 'teal', font: 'inter', density: 'compact' }); readAppearance(); // { theme: 'auto', accent: 'teal', font: 'inter', radius: 'default', … }

setAppearance changes only the settings it is given, and checks them all first: an unknown value throws and changes nothing.

Density scales the heights of controls and fields — a button is 34px in compact, 46px in comfortable, on even pixels — the padding and gap tokens, and table cells. Text size scales every type token and table text. Both compose with the mobile and ultra-wide scales. A few inner sizes — calendar cells, the small segmented control — keep their own.

The rounding of heights uses CSS round() where the browser has it (Chrome 125, Firefox 118, Safari 15.4); before that, heights keep their exact fraction and nothing else changes.

Kanto ships its own faces only. With inter, plex or geist, load the font yourself (Google Fonts has all three); without it, the system face shows.

The inputs behind the attributes are --radius-scale, --density-scale, --text-scale, --font-body and --font-display. Set in CSS, they need one of the attributes on the same element for the tokens to re-read them there; --font-body and --font-display take a full font stack, the scales a plain number.

An element carrying one of these attributes re-declares Kanto's spacing and type tokens from these inputs, so an app that overrides a token itself — :root { --button-height: 36px } — repeats the override for that element, or overrides the input instead. The same goes for colour tokens under data-accent.

Conventions

  • Surfaces are a ramp, not a shadow scale. Elevation means a lighter surface: --color-dark-12 (page) → --color-dark-16 (card) → --color-dark-20 (raised). Shadows are kept for what floats over the page: toasts, and popovers in the light theme.
  • Popovers have their own surface. Lists, menus and calendars that open over the page use --surface-popover, --border-popover, --shadow-popover and, for lines inside them, --divider-popover. In the dark the ramp sets them apart alone — a lighter surface, no edge, no shadow. In the light a darker step reads as a grey smudge and fades what sits on it, and white alone is lost on a near-white page: white, then, with an edge and a soft shadow.
  • Semantic colours come in four variants. -base is opaque, for solid fills; -soft is the same hue at 12% alpha, for tinted backgrounds; -hover is 16%; -text is for text and icons. Never colour text with -base: it is tuned to carry white text, which makes it too dark on a dark surface and too light on a light one. Every -text holds WCAG AA (4.5:1) on the page, card and raised surfaces and on its own tint. --color-danger-solid is the red for a filled destructive button.
  • Prefer the aliases. --surface-card over --color-dark-16, --text-muted over --color-text-400. The aliases survive a change to which ramp step a role uses.
  • Type is applied whole. font: var(--font-normal-medium) sets weight, size, line height and family in one declaration; there is no --font-size-* scale to get half-applied.
  • Inputs take an outline, never a border. A border would reflow the field on focus; --outline-width sits outside the box.