Token specimen

Version 1.6.0. Every semantic token in both modes. The theme switch and width toggle are the shipped controls.js.

Use it

Pick one way to load the tokens. Most sites want the CDN link.

CDN link

<link rel="stylesheet" href="https://design.manjunathhk.in/v1/index.css">

npm CSS import

@import "@manjunathhk/design-tokens/index.css";

Theme override

<html data-theme="dark"></html>

Colour swatches and contrast ratios

Swatches show the emitted value. Each mode lists every foreground and background pair the contrast contract checks.

Light swatches

color.accent#B45309
color.accent-hover#92400E
color.accent-subtlergba(180, 83, 9, 0.14)
color.bg#FAFAF9
color.bg-subtle#F5F5F4
color.border#E7E5E4
color.border-control#78716C
color.border-strong#78716C
color.danger#B91C1C
color.focus-ring#B45309
color.grid-linergba(28, 25, 23, 0.05)
color.info#1D4ED8
color.on-accent#FFFFFF
color.on-danger#FFFFFF
color.success#15803D
color.surface#FFFFFF
color.text#1C1917
color.text-muted#57534E
color.text-secondary#44403C
color.warning#A16207

Dark swatches

color.accent#FBBF24
color.accent-hover#FCD34D
color.accent-subtlergba(251, 191, 36, 0.14)
color.bg#0C0A09
color.bg-subtle#1C1917
color.border#44403C
color.border-control#78716C
color.border-strong#78716C
color.danger#F87171
color.focus-ring#FBBF24
color.grid-linergba(255, 255, 255, 0.04)
color.info#60A5FA
color.on-accent#1C1917
color.on-danger#1C1917
color.success#4ADE80
color.surface#292524
color.text#FAFAF9
color.text-muted#A8A29E
color.text-secondary#D6D3D1
color.warning#FB923C
Light contrast ratios32 pairs pass · lowest 4.40
FG tokenFG valueBG tokenBG valueRatioRuleResult
color.text #1C1917 color.bg #FAFAF9 16.74 ≥ 4.5 ✓ pass
color.text #1C1917 color.bg-subtle #F5F5F4 16.03 ≥ 4.5 ✓ pass
color.text #1C1917 color.surface #FFFFFF 17.49 ≥ 4.5 ✓ pass
color.text-secondary #44403C color.bg #FAFAF9 9.84 ≥ 4.5 ✓ pass
color.text-secondary #44403C color.bg-subtle #F5F5F4 9.42 ≥ 4.5 ✓ pass
color.text-secondary #44403C color.surface #FFFFFF 10.27 ≥ 4.5 ✓ pass
color.text-muted #57534E color.bg #FAFAF9 7.30 ≥ 4.5 ✓ pass
color.text-muted #57534E color.bg-subtle #F5F5F4 6.99 ≥ 4.5 ✓ pass
color.text-muted #57534E color.surface #FFFFFF 7.63 ≥ 4.5 ✓ pass
color.danger #B91C1C color.bg #FAFAF9 6.19 ≥ 4.5 ✓ pass
color.danger #B91C1C color.bg-subtle #F5F5F4 5.93 ≥ 4.5 ✓ pass
color.danger #B91C1C color.surface #FFFFFF 6.47 ≥ 4.5 ✓ pass
color.success #15803D color.bg #FAFAF9 4.80 ≥ 4.5 ✓ pass
color.success #15803D color.bg-subtle #F5F5F4 4.60 ≥ 4.5 ✓ pass
color.success #15803D color.surface #FFFFFF 5.02 ≥ 4.5 ✓ pass
color.warning #A16207 color.bg #FAFAF9 4.71 ≥ 4.5 ✓ pass
color.warning #A16207 color.bg-subtle #F5F5F4 4.51 ≥ 4.5 ✓ pass
color.warning #A16207 color.surface #FFFFFF 4.92 ≥ 4.5 ✓ pass
color.info #1D4ED8 color.bg #FAFAF9 6.42 ≥ 4.5 ✓ pass
color.info #1D4ED8 color.bg-subtle #F5F5F4 6.14 ≥ 4.5 ✓ pass
color.info #1D4ED8 color.surface #FFFFFF 6.70 ≥ 4.5 ✓ pass
color.accent #B45309 color.bg #FAFAF9 4.81 ≥ 4.5 ✓ pass
color.accent #B45309 color.surface #FFFFFF 5.02 ≥ 4.5 ✓ pass
color.on-accent #FFFFFF color.accent #B45309 5.02 ≥ 4.5 ✓ pass
color.on-accent #FFFFFF color.accent-hover #92400E 7.09 ≥ 4.5 ✓ pass
color.on-danger #FFFFFF color.danger #B91C1C 6.47 ≥ 4.5 ✓ pass
color.border-control #78716C color.bg #FAFAF9 4.59 ≥ 3 ✓ pass
color.border-control #78716C color.bg-subtle #F5F5F4 4.40 ≥ 3 ✓ pass
color.border-control #78716C color.surface #FFFFFF 4.80 ≥ 3 ✓ pass
color.focus-ring #B45309 color.bg #FAFAF9 4.81 ≥ 3 ✓ pass
color.focus-ring #B45309 color.bg-subtle #F5F5F4 4.60 ≥ 3 ✓ pass
color.focus-ring #B45309 color.surface #FFFFFF 5.02 ≥ 3 ✓ pass
Dark contrast ratios32 pairs pass · lowest 3.16
FG tokenFG valueBG tokenBG valueRatioRuleResult
color.text #FAFAF9 color.bg #0C0A09 18.92 ≥ 4.5 ✓ pass
color.text #FAFAF9 color.bg-subtle #1C1917 16.74 ≥ 4.5 ✓ pass
color.text #FAFAF9 color.surface #292524 14.52 ≥ 4.5 ✓ pass
color.text-secondary #D6D3D1 color.bg #0C0A09 13.26 ≥ 4.5 ✓ pass
color.text-secondary #D6D3D1 color.bg-subtle #1C1917 11.74 ≥ 4.5 ✓ pass
color.text-secondary #D6D3D1 color.surface #292524 10.18 ≥ 4.5 ✓ pass
color.text-muted #A8A29E color.bg #0C0A09 7.83 ≥ 4.5 ✓ pass
color.text-muted #A8A29E color.bg-subtle #1C1917 6.93 ≥ 4.5 ✓ pass
color.text-muted #A8A29E color.surface #292524 6.01 ≥ 4.5 ✓ pass
color.danger #F87171 color.bg #0C0A09 7.14 ≥ 4.5 ✓ pass
color.danger #F87171 color.bg-subtle #1C1917 6.32 ≥ 4.5 ✓ pass
color.danger #F87171 color.surface #292524 5.48 ≥ 4.5 ✓ pass
color.success #4ADE80 color.bg #0C0A09 11.34 ≥ 4.5 ✓ pass
color.success #4ADE80 color.bg-subtle #1C1917 10.04 ≥ 4.5 ✓ pass
color.success #4ADE80 color.surface #292524 8.71 ≥ 4.5 ✓ pass
color.warning #FB923C color.bg #0C0A09 8.73 ≥ 4.5 ✓ pass
color.warning #FB923C color.bg-subtle #1C1917 7.73 ≥ 4.5 ✓ pass
color.warning #FB923C color.surface #292524 6.70 ≥ 4.5 ✓ pass
color.info #60A5FA color.bg #0C0A09 7.77 ≥ 4.5 ✓ pass
color.info #60A5FA color.bg-subtle #1C1917 6.88 ≥ 4.5 ✓ pass
color.info #60A5FA color.surface #292524 5.97 ≥ 4.5 ✓ pass
color.accent #FBBF24 color.bg #0C0A09 11.83 ≥ 4.5 ✓ pass
color.accent #FBBF24 color.surface #292524 9.09 ≥ 4.5 ✓ pass
color.on-accent #1C1917 color.accent #FBBF24 10.48 ≥ 4.5 ✓ pass
color.on-accent #1C1917 color.accent-hover #FCD34D 12.13 ≥ 4.5 ✓ pass
color.on-danger #1C1917 color.danger #F87171 6.32 ≥ 4.5 ✓ pass
color.border-control #78716C color.bg #0C0A09 4.12 ≥ 3 ✓ pass
color.border-control #78716C color.bg-subtle #1C1917 3.65 ≥ 3 ✓ pass
color.border-control #78716C color.surface #292524 3.16 ≥ 3 ✓ pass
color.focus-ring #FBBF24 color.bg #0C0A09 11.83 ≥ 3 ✓ pass
color.focus-ring #FBBF24 color.bg-subtle #1C1917 10.48 ≥ 3 ✓ pass
color.focus-ring #FBBF24 color.surface #292524 9.09 ≥ 3 ✓ pass

In use

Tokens working together. Each preview is its own page holding only the token rules, base.css and the snippets under it, so it shows what a site gets from index.css plus those snippets.

Copy the HTML and the group CSS into a site and rename the ex- classes to suit it. Copied code belongs to that site: it is not part of the versioned package and carries no compatibility promise (D53).

Actions

Buttons and links that look like buttons. One primary action per view.

Tokens used --mk-spacing-2 --mk-spacing-4 --mk-color-border-control --mk-radius-md --mk-color-surface --mk-color-text --mk-font-size-sm --mk-font-weight-semibold --mk-font-line-height-tight --mk-motion-duration-fast --mk-motion-ease --mk-color-accent-subtle --mk-color-accent --mk-color-on-accent --mk-color-accent-hover --mk-color-danger --mk-color-on-danger --mk-color-bg-subtle --mk-color-text-muted --mk-spacing-3

Actions CSS
/* Buttons: one base class plus a variant. Hover, focus and active come from
   the same rules; the focus outline comes from base.css. */
.ex-button {
  display: inline-flex;
  gap: var(--mk-spacing-2);
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  padding: var(--mk-spacing-2) var(--mk-spacing-4);
  border: 1px solid var(--mk-color-border-control);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-semibold);
  line-height: var(--mk-font-line-height-tight);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--mk-motion-duration-fast) var(--mk-motion-ease),
    box-shadow var(--mk-motion-duration-fast) var(--mk-motion-ease),
    transform var(--mk-motion-duration-fast) var(--mk-motion-ease);
}

.ex-button:hover {
  background-color: var(--mk-color-accent-subtle);
}

.ex-button:active {
  transform: translateY(1px);
}

/* Primary: the one main action on a view. */
.ex-button-primary {
  border-color: var(--mk-color-accent);
  background-color: var(--mk-color-accent);
  color: var(--mk-color-on-accent);
}

.ex-button-primary:hover {
  border-color: var(--mk-color-accent-hover);
  background-color: var(--mk-color-accent-hover);
}

/* Quiet: low-emphasis actions next to a primary one. */
.ex-button-quiet {
  border-color: transparent;
  background-color: transparent;
  color: var(--mk-color-accent);
}

/* accent on accent-subtle is 3.99:1 in light mode, below 4.5:1, so the label
   switches to the text colour on hover. */
.ex-button-quiet:hover {
  color: var(--mk-color-text);
}

/* Destructive, outlined: a lower-emphasis delete next to other actions. */
.ex-button-danger {
  border-color: var(--mk-color-danger);
  color: var(--mk-color-danger);
}

/* The fill would compete with the danger colour, so hover thickens the edge. */
.ex-button-danger:hover {
  background-color: var(--mk-color-surface);
  box-shadow: inset 0 0 0 1px var(--mk-color-danger);
}

/* Destructive, filled: the confirming action for something that cannot be
   undone. No danger-hover token exists, so hover underlines the label. */
.ex-button-danger-solid {
  border-color: var(--mk-color-danger);
  background-color: var(--mk-color-danger);
  color: var(--mk-color-on-danger);
}

.ex-button-danger-solid:hover {
  background-color: var(--mk-color-danger);
  text-decoration: underline;
}

/* Disabled: no disabled colour tokens exist yet, so it is drawn with the
   subtle background, muted text and a dashed outline. */
.ex-button:disabled,
.ex-button[aria-disabled="true"] {
  border-style: dashed;
  border-color: var(--mk-color-border-control);
  background-color: var(--mk-color-bg-subtle);
  color: var(--mk-color-text-muted);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

/* Icons inherit the label colour. */
.ex-button svg {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
}

.ex-button-icon {
  padding: var(--mk-spacing-2);
  min-width: 2.5rem;
}

.ex-button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-3);
  align-items: center;
}

Button variants

Primary, secondary, quiet, and destructive outlined and filled. Forced below it: hover, keyboard focus, pressed.

Button variants HTML
<div class="ex-button-row">
  <button type="button" class="ex-button ex-button-primary">Save changes</button>
  <button type="button" class="ex-button">Preview</button>
  <button type="button" class="ex-button ex-button-quiet">Cancel</button>
  <button type="button" class="ex-button ex-button-danger">Delete</button>
  <button type="button" class="ex-button ex-button-danger-solid">Delete forever</button>
</div>

Buttons with icons

Icons take the label colour. An icon-only button needs an aria-label. Forced below it: hover, keyboard focus, pressed.

Buttons with icons HTML
<div class="ex-button-row">
  <button type="button" class="ex-button ex-button-primary">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <path d="M12 5v14M5 12h14" />
    </svg>
    New post
  </button>
  <button type="button" class="ex-button">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <path d="M12 3v12M7 10l5 5 5-5M5 21h14" />
    </svg>
    Download
  </button>
  <button type="button" class="ex-button ex-button-icon" aria-label="Settings">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <circle cx="12" cy="12" r="3" />
      <path
        d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"
      />
    </svg>
  </button>
</div>

Links styled as buttons

Use a link when the action navigates, a button when it does something. Forced below it: hover, keyboard focus, pressed.

Links styled as buttons HTML
<div class="ex-button-row">
  <a class="ex-button ex-button-primary" href="#get-started">Get started</a>
  <a class="ex-button" href="#docs">Read the docs</a>
  <a class="ex-button ex-button-quiet" href="#changelog">Changelog</a>
</div>

Disabled buttons

Prefer explaining why an action is unavailable over disabling it silently.

Disabled buttons HTML
<div class="ex-button-row">
  <button type="button" class="ex-button ex-button-primary" disabled>Save changes</button>
  <button type="button" class="ex-button" disabled>Preview</button>
  <button type="button" class="ex-button ex-button-quiet" disabled>Cancel</button>
  <button type="button" class="ex-button ex-button-danger" disabled>Delete</button>
  <button type="button" class="ex-button ex-button-danger-solid" disabled>Delete forever</button>
</div>

Forms

Labels above controls, help and messages below. Controls use border-control, which meets 3:1.

Tokens used --mk-spacing-1 --mk-spacing-5 --mk-color-text --mk-font-size-sm --mk-font-weight-semibold --mk-color-text-muted --mk-font-weight-regular --mk-spacing-2 --mk-spacing-3 --mk-color-border-control --mk-radius-md --mk-color-surface --mk-font-size-base --mk-font-line-height-normal --mk-motion-duration-fast --mk-motion-ease --mk-color-focus-ring --mk-color-danger --mk-color-success --mk-font-weight-medium --mk-color-bg-subtle --mk-spacing-4 --mk-color-border --mk-color-accent --mk-color-text-secondary

Forms CSS
/* A field is a label, a control and optional help or message text, stacked. */
.ex-field {
  display: grid;
  gap: var(--mk-spacing-1);
  max-width: 28rem;
}

.ex-form {
  display: grid;
  gap: var(--mk-spacing-5);
}

.ex-label {
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-semibold);
}

.ex-optional {
  color: var(--mk-color-text-muted);
  font-weight: var(--mk-font-weight-regular);
}

.ex-help {
  color: var(--mk-color-text-muted);
  font-size: var(--mk-font-size-sm);
}

/* Text-like controls share one look: surface fill, 3:1 control border. */
.ex-input,
.ex-select,
.ex-textarea {
  width: 100%;
  min-height: 2.5rem;
  padding: var(--mk-spacing-2) var(--mk-spacing-3);
  border: 1px solid var(--mk-color-border-control);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-base);
  line-height: var(--mk-font-line-height-normal);
  transition: border-color var(--mk-motion-duration-fast) var(--mk-motion-ease);
}

.ex-textarea {
  min-height: 6rem;
  resize: vertical;
}

.ex-input::placeholder,
.ex-textarea::placeholder {
  color: var(--mk-color-text-muted);
  opacity: 1;
}

/* base.css draws the focus outline; the border follows it. */
.ex-input:focus-visible,
.ex-select:focus-visible,
.ex-textarea:focus-visible {
  border-color: var(--mk-color-focus-ring);
}

/* Validation: the colour is never the only signal; the message says it too. */
.ex-input[aria-invalid="true"],
.ex-textarea[aria-invalid="true"] {
  border-color: var(--mk-color-danger);
}

.ex-input.ex-is-valid {
  border-color: var(--mk-color-success);
}

.ex-message {
  display: flex;
  gap: var(--mk-spacing-1);
  align-items: center;
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-medium);
}

.ex-message svg {
  width: 1rem;
  height: 1rem;
  flex: none;
}

.ex-message-error {
  color: var(--mk-color-danger);
}

.ex-message-success {
  color: var(--mk-color-success);
}

/* Disabled: drawn with existing tokens (see "Not yet expressible"). */
.ex-input:disabled,
.ex-select:disabled,
.ex-textarea:disabled {
  border-style: dashed;
  background-color: var(--mk-color-bg-subtle);
  color: var(--mk-color-text-muted);
  cursor: not-allowed;
}

/* Checkboxes, radios and ranges keep their native drawing; accent-color
   tints them, and the browser picks a contrasting check mark. */
.ex-fieldset {
  display: grid;
  gap: var(--mk-spacing-2);
  margin: 0;
  padding: var(--mk-spacing-4);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-md);
}

.ex-fieldset legend {
  padding: 0 var(--mk-spacing-1);
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-semibold);
}

.ex-choice {
  display: flex;
  gap: var(--mk-spacing-2);
  align-items: center;
  font-size: var(--mk-font-size-base);
  cursor: pointer;
}

.ex-choice input,
.ex-range {
  margin: 0;
  accent-color: var(--mk-color-accent);
}

.ex-choice input {
  width: 1.125rem;
  height: 1.125rem;
}

.ex-range {
  width: 100%;
}

.ex-output {
  color: var(--mk-color-text-secondary);
  font-size: var(--mk-font-size-sm);
  font-variant-numeric: tabular-nums;
}

Text fields

Label, input, optional marker and help text tied with aria-describedby. Forced below it: keyboard focus.

Text fields HTML
<div class="ex-form">
  <div class="ex-field">
    <label class="ex-label" for="ex-name">Full name</label>
    <input class="ex-input" id="ex-name" name="name" autocomplete="name" />
  </div>
  <div class="ex-field">
    <label class="ex-label" for="ex-email">Email</label>
    <input
      class="ex-input"
      id="ex-email"
      name="email"
      type="email"
      placeholder="you@example.com"
      aria-describedby="ex-email-help"
    />
    <p class="ex-help" id="ex-email-help">Only used to reply to you.</p>
  </div>
  <div class="ex-field">
    <label class="ex-label" for="ex-site"
      >Website <span class="ex-optional">(optional)</span></label
    >
    <input class="ex-input" id="ex-site" name="site" type="url" placeholder="https://" />
  </div>
</div>

Validation messages

The message carries the meaning; the border colour only reinforces it.

Validation messages HTML
<div class="ex-form">
  <div class="ex-field">
    <label class="ex-label" for="ex-username">Username</label>
    <input
      class="ex-input"
      id="ex-username"
      value="mk"
      aria-invalid="true"
      aria-describedby="ex-username-error"
    />
    <p class="ex-message ex-message-error" id="ex-username-error">
      <svg
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        aria-hidden="true"
      >
        <circle cx="12" cy="12" r="9" />
        <path d="M12 7v6M12 17h.01" />
      </svg>
      Use at least 3 characters.
    </p>
  </div>
  <div class="ex-field">
    <label class="ex-label" for="ex-handle">Handle</label>
    <input
      class="ex-input ex-is-valid"
      id="ex-handle"
      value="manjunathhk"
      aria-describedby="ex-handle-ok"
    />
    <p class="ex-message ex-message-success" id="ex-handle-ok">
      <svg
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        aria-hidden="true"
      >
        <path d="M5 12l5 5 9-10" />
      </svg>
      Available.
    </p>
  </div>
</div>

Select and textarea

Native controls, styled like text fields. Forced below it: keyboard focus.

Select and textarea HTML
<div class="ex-form">
  <div class="ex-field">
    <label class="ex-label" for="ex-topic">Topic</label>
    <select class="ex-select" id="ex-topic">
      <option>General question</option>
      <option>Project enquiry</option>
      <option>Speaking</option>
    </select>
  </div>
  <div class="ex-field">
    <label class="ex-label" for="ex-message">Message</label>
    <textarea
      class="ex-textarea"
      id="ex-message"
      placeholder="What would you like to talk about?"
    ></textarea>
  </div>
</div>

Checkboxes and radios

Native controls tinted with accent-color, grouped in a fieldset with a legend. Forced below it: keyboard focus.

Checkboxes and radios HTML
<div class="ex-form">
  <fieldset class="ex-fieldset">
    <legend>Notify me about</legend>
    <label class="ex-choice"><input type="checkbox" checked /> New posts</label>
    <label class="ex-choice"><input type="checkbox" /> Talks and events</label>
  </fieldset>
  <fieldset class="ex-fieldset">
    <legend>Frequency</legend>
    <label class="ex-choice"
      ><input type="radio" name="ex-frequency" checked /> Weekly digest</label
    >
    <label class="ex-choice"><input type="radio" name="ex-frequency" /> Every post</label>
  </fieldset>
</div>

Range

A native slider with its value in an output element. Forced below it: keyboard focus.

Range HTML
<div class="ex-field">
  <label class="ex-label" for="ex-speed">Reading speed</label>
  <input
    class="ex-range"
    id="ex-speed"
    type="range"
    min="0"
    max="100"
    value="60"
    aria-describedby="ex-speed-value"
  />
  <output class="ex-output" id="ex-speed-value" for="ex-speed">60 percent</output>
</div>

Disabled fields

Drawn with existing tokens; see Not yet expressible.

Disabled fields HTML
<div class="ex-form">
  <div class="ex-field">
    <label class="ex-label" for="ex-plan">Plan</label>
    <input class="ex-input" id="ex-plan" value="Personal" disabled />
    <p class="ex-help">Plans change at renewal.</p>
  </div>
  <div class="ex-field">
    <label class="ex-label" for="ex-region">Region</label>
    <select class="ex-select" id="ex-region" disabled>
      <option>Europe</option>
    </select>
  </div>
</div>

Feedback

Status colours always come with an icon or words, never colour alone.

Tokens used --mk-spacing-1 --mk-spacing-3 --mk-spacing-4 --mk-color-border --mk-radius-md --mk-color-surface --mk-font-weight-semibold --mk-color-text --mk-font-size-sm --mk-color-info --mk-color-success --mk-color-warning --mk-color-danger --mk-spacing-2 --mk-radius-pill --mk-font-size-xs --mk-font-letter-spacing-label --mk-font-line-height-normal --mk-color-border-control --mk-color-text-secondary --mk-color-accent-subtle --mk-z-index-toast --mk-radius-lg --mk-shadow-raised --mk-radius-sm --mk-color-text-muted --mk-font-weight-regular --mk-color-accent --mk-spacing-10 --mk-spacing-6 --mk-color-bg-subtle --mk-font-size-lg --mk-motion-duration-slower --mk-motion-ease

Feedback CSS
/* Alerts: surface fill with a status-coloured edge, icon and title. No
   status tint tokens exist yet (see "Not yet expressible"). */
.ex-alert {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--mk-spacing-1) var(--mk-spacing-3);
  padding: var(--mk-spacing-3) var(--mk-spacing-4);
  border: 1px solid var(--mk-color-border);
  border-left: 4px solid currentcolor;
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
}

.ex-alert svg {
  grid-row: span 2;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.125rem;
}

.ex-alert-title {
  font-weight: var(--mk-font-weight-semibold);
}

.ex-alert-body {
  grid-column: 2;
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-sm);
}

.ex-alert-info {
  color: var(--mk-color-info);
}

.ex-alert-success {
  color: var(--mk-color-success);
}

.ex-alert-warning {
  color: var(--mk-color-warning);
}

.ex-alert-danger {
  color: var(--mk-color-danger);
}

.ex-stack {
  display: grid;
  gap: var(--mk-spacing-3);
}

/* Badges: outlined text in the status colour, which meets 4.5:1 on every
   background. Filled badges need on-status tokens that do not exist yet. */
.ex-badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-2);
  align-items: center;
}

.ex-badge {
  display: inline-flex;
  gap: var(--mk-spacing-1);
  align-items: center;
  padding: 0.125rem var(--mk-spacing-2);
  border: 1px solid currentcolor;
  border-radius: var(--mk-radius-pill);
  font-size: var(--mk-font-size-xs);
  font-weight: var(--mk-font-weight-semibold);
  letter-spacing: var(--mk-font-letter-spacing-label);
  line-height: var(--mk-font-line-height-normal);
  white-space: nowrap;
}

.ex-badge-neutral {
  border-color: var(--mk-color-border-control);
  color: var(--mk-color-text-secondary);
}

.ex-badge-accent {
  border-color: transparent;
  background-color: var(--mk-color-accent-subtle);
  color: var(--mk-color-text);
}

.ex-badge-info {
  color: var(--mk-color-info);
}

.ex-badge-success {
  color: var(--mk-color-success);
}

.ex-badge-warning {
  color: var(--mk-color-warning);
}

.ex-badge-danger {
  color: var(--mk-color-danger);
}

/* Toast: fixed to the corner, above page content. */
.ex-toast {
  position: fixed;
  right: var(--mk-spacing-4);
  bottom: var(--mk-spacing-4);
  z-index: var(--mk-z-index-toast);
  display: flex;
  gap: var(--mk-spacing-3);
  align-items: center;
  max-width: min(24rem, calc(100% - 2 * var(--mk-spacing-4)));
  padding: var(--mk-spacing-3) var(--mk-spacing-4);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
  font-size: var(--mk-font-size-sm);
}

.ex-toast-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  color: var(--mk-color-success);
}

.ex-toast-text {
  flex: 1;
}

.ex-toast-close {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--mk-radius-sm);
  background-color: transparent;
  color: var(--mk-color-text-muted);
  cursor: pointer;
}

.ex-toast-close:hover {
  background-color: var(--mk-color-accent-subtle);
  color: var(--mk-color-text);
}

.ex-toast-close svg {
  width: 1rem;
  height: 1rem;
}

/* Progress keeps its native drawing, tinted by accent-color. */
.ex-progress-field {
  display: grid;
  gap: var(--mk-spacing-1);
  max-width: 28rem;
}

.ex-progress-label {
  display: flex;
  justify-content: space-between;
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-semibold);
}

.ex-progress-value {
  color: var(--mk-color-text-secondary);
  font-weight: var(--mk-font-weight-regular);
  font-variant-numeric: tabular-nums;
}

.ex-progress {
  width: 100%;
  height: var(--mk-spacing-2);
  accent-color: var(--mk-color-accent);
}

/* Empty state: centred icon, heading, one sentence and one action. */
.ex-empty {
  display: grid;
  gap: var(--mk-spacing-3);
  justify-items: center;
  padding: var(--mk-spacing-10) var(--mk-spacing-6);
  border: 1px dashed var(--mk-color-border-control);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-bg-subtle);
  text-align: center;
}

.ex-empty svg {
  width: 2.5rem;
  height: 2.5rem;
  color: var(--mk-color-text-muted);
}

.ex-empty-title {
  font-size: var(--mk-font-size-lg);
}

.ex-empty-text {
  color: var(--mk-color-text-secondary);
}

/* Loading: a spinner beside words that say what is loading, and a skeleton
   that holds the shape of the content to come. Under reduced motion base.css
   stops both animations; the words still carry the state. */
.ex-loading {
  display: flex;
  gap: var(--mk-spacing-2);
  align-items: center;
  color: var(--mk-color-text-secondary);
  font-size: var(--mk-font-size-sm);
}

.ex-spinner {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  border: 2px solid var(--mk-color-border);
  border-top-color: var(--mk-color-accent);
  border-radius: var(--mk-radius-pill);
  animation: ex-spin var(--mk-motion-duration-slower) linear infinite;
}

@keyframes ex-spin {
  to {
    transform: rotate(1turn);
  }
}

.ex-skeleton-card {
  display: grid;
  gap: var(--mk-spacing-2);
  max-width: 28rem;
  padding: var(--mk-spacing-4);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-surface);
}

.ex-skeleton {
  display: block;
  height: 0.75rem;
  border-radius: var(--mk-radius-sm);
  background-color: var(--mk-color-border);
  animation: ex-pulse calc(2 * var(--mk-motion-duration-slower)) var(--mk-motion-ease) infinite
    alternate;
}

.ex-skeleton-eyebrow {
  width: 25%;
}

.ex-skeleton-title {
  width: 75%;
  height: 1.25rem;
  margin-bottom: var(--mk-spacing-1);
}

.ex-skeleton-short {
  width: 60%;
}

@keyframes ex-pulse {
  to {
    opacity: 0.4;
  }
}

.ex-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

Alerts

Info, success, warning and danger. Use role=alert only for danger.

Alerts HTML
<div class="ex-stack">
  <div class="ex-alert ex-alert-info" role="status">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      aria-hidden="true"
    >
      <circle cx="12" cy="12" r="9" />
      <path d="M12 11v6M12 7h.01" />
    </svg>
    <p class="ex-alert-title">New version available</p>
    <p class="ex-alert-body">Reload the page to get the latest changes.</p>
  </div>
  <div class="ex-alert ex-alert-success" role="status">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <circle cx="12" cy="12" r="9" />
      <path d="M8 12l3 3 5-6" />
    </svg>
    <p class="ex-alert-title">Message sent</p>
    <p class="ex-alert-body">Thanks for writing. Expect a reply within two days.</p>
  </div>
  <div class="ex-alert ex-alert-warning" role="status">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <path d="M12 3l9.5 17h-19L12 3Z" />
      <path d="M12 10v4M12 17h.01" />
    </svg>
    <p class="ex-alert-title">Draft not saved</p>
    <p class="ex-alert-body">You are offline. Changes stay in this tab until you reconnect.</p>
  </div>
  <div class="ex-alert ex-alert-danger" role="alert">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      aria-hidden="true"
    >
      <circle cx="12" cy="12" r="9" />
      <path d="M9 9l6 6M15 9l-6 6" />
    </svg>
    <p class="ex-alert-title">Payment failed</p>
    <p class="ex-alert-body">The card was declined. Try another card or contact your bank.</p>
  </div>
</div>

Badges and tags

Short labels for status or category.

Badges and tags HTML
<div class="ex-badge-row">
  <span class="ex-badge ex-badge-neutral">Draft</span>
  <span class="ex-badge ex-badge-accent">Featured</span>
  <span class="ex-badge ex-badge-info">Beta</span>
  <span class="ex-badge ex-badge-success">Published</span>
  <span class="ex-badge ex-badge-warning">Review due</span>
  <span class="ex-badge ex-badge-danger">Archived</span>
</div>

Toast

Fixed to the bottom corner on z-index.toast; shown here inside its preview.

Toast HTML
<div class="ex-toast" role="status">
  <svg
    class="ex-toast-icon"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    aria-hidden="true"
  >
    <path d="M5 12l5 5 9-10" />
  </svg>
  <p class="ex-toast-text">Link copied to the clipboard.</p>
  <button type="button" class="ex-toast-close" aria-label="Dismiss">
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      aria-hidden="true"
    >
      <path d="M6 6l12 12M18 6L6 18" />
    </svg>
  </button>
</div>

Progress

Native progress, determinate and indeterminate.

Progress HTML
<div class="ex-stack">
  <div class="ex-progress-field">
    <div class="ex-progress-label">
      <label for="ex-upload">Uploading photos</label>
      <span class="ex-progress-value">60%</span>
    </div>
    <progress class="ex-progress" id="ex-upload" max="100" value="60">60%</progress>
  </div>
  <div class="ex-progress-field">
    <div class="ex-progress-label"><label for="ex-sync">Syncing</label></div>
    <progress class="ex-progress" id="ex-sync">Syncing</progress>
  </div>
</div>

Loading

A spinner beside words, and a skeleton in the shape of the content. Under reduced motion both stand still; the words carry the state.

Loading HTML
<div class="ex-stack">
  <p class="ex-loading" role="status">
    <span class="ex-spinner" aria-hidden="true"></span>
    Loading articles…
  </p>
  <div class="ex-skeleton-card" role="status" aria-busy="true">
    <span class="ex-visually-hidden">Loading article</span>
    <span class="ex-skeleton ex-skeleton-eyebrow" aria-hidden="true"></span>
    <span class="ex-skeleton ex-skeleton-title" aria-hidden="true"></span>
    <span class="ex-skeleton" aria-hidden="true"></span>
    <span class="ex-skeleton ex-skeleton-short" aria-hidden="true"></span>
  </div>
</div>

Empty state

Says what is missing and offers the next step. Uses the Actions CSS for its button.

Empty state HTML
<div class="ex-empty">
  <svg
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="1.5"
    stroke-linecap="round"
    stroke-linejoin="round"
    aria-hidden="true"
  >
    <path d="M4 7h16v12H4zM4 7l2-3h12l2 3M9 12h6" />
  </svg>
  <h2 class="ex-empty-title">No saved articles yet</h2>
  <p class="ex-empty-text">Save an article to read it later, even offline.</p>
  <a class="ex-button ex-button-primary" href="#articles">Browse articles</a>
</div>

Content

Cards, long-form text and data tables. base.css already sets reading widths and tabular numerals.

Tokens used --mk-spacing-6 --mk-radius-lg --mk-color-bg-subtle --mk-spacing-4 --mk-spacing-2 --mk-spacing-5 --mk-color-border --mk-color-surface --mk-shadow-raised --mk-color-text-muted --mk-font-size-xs --mk-font-weight-semibold --mk-font-letter-spacing-label --mk-font-size-lg --mk-color-text-secondary --mk-color-border-control --mk-color-accent --mk-font-line-height-relaxed --mk-font-size-2xl --mk-font-letter-spacing-display --mk-font-line-height-tight --mk-font-size-xl --mk-font-size-md --mk-spacing-1 --mk-radius-sm --mk-radius-md --mk-font-size-sm --mk-font-line-height-normal --mk-spacing-3 --mk-color-accent-subtle

Content CSS
/* Cards sit on surface inside a bg-subtle section, raised by shadow.raised. */
.ex-section {
  padding: var(--mk-spacing-6);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-bg-subtle);
}

.ex-card-grid {
  display: grid;
  gap: var(--mk-spacing-4);
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
}

.ex-card {
  display: grid;
  gap: var(--mk-spacing-2);
  align-content: start;
  padding: var(--mk-spacing-5);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
}

.ex-eyebrow {
  color: var(--mk-color-text-muted);
  font-size: var(--mk-font-size-xs);
  font-weight: var(--mk-font-weight-semibold);
  letter-spacing: var(--mk-font-letter-spacing-label);
  text-transform: uppercase;
}

.ex-card-title {
  font-size: var(--mk-font-size-lg);
}

.ex-card-text {
  color: var(--mk-color-text-secondary);
}

/* A whole card as one link: the title carries the link, the card shows it. */
.ex-card-link {
  color: inherit;
  text-decoration: none;
}

.ex-card:has(.ex-card-link:hover) {
  border-color: var(--mk-color-border-control);
}

.ex-card-link:hover {
  color: var(--mk-color-accent);
  text-decoration: underline;
}

/* Long-form text. base.css already caps p and li at 65ch. */
.ex-prose {
  display: grid;
  gap: var(--mk-spacing-4);
  line-height: var(--mk-font-line-height-relaxed);
}

.ex-prose h1 {
  font-size: var(--mk-font-size-2xl);
  letter-spacing: var(--mk-font-letter-spacing-display);
  line-height: var(--mk-font-line-height-tight);
}

.ex-prose h2 {
  margin-top: var(--mk-spacing-4);
  font-size: var(--mk-font-size-xl);
}

.ex-prose h3 {
  font-size: var(--mk-font-size-lg);
}

.ex-prose h4 {
  font-size: var(--mk-font-size-md);
}

.ex-prose ul,
.ex-prose ol {
  display: grid;
  gap: var(--mk-spacing-1);
  margin: 0;
  padding-left: var(--mk-spacing-6);
}

.ex-prose li::marker {
  color: var(--mk-color-text-muted);
}

.ex-prose blockquote {
  padding-left: var(--mk-spacing-4);
  border-left: 3px solid var(--mk-color-accent);
  color: var(--mk-color-text-secondary);
  font-style: italic;
}

.ex-prose code {
  padding: 0.125em 0.375em;
  border-radius: var(--mk-radius-sm);
  background-color: var(--mk-color-bg-subtle);
  font-size: 0.9em;
}

.ex-prose kbd {
  padding: 0.125em 0.375em;
  border: 1px solid var(--mk-color-border-control);
  border-bottom-width: 2px;
  border-radius: var(--mk-radius-sm);
  background-color: var(--mk-color-surface);
  font-size: 0.85em;
}

.ex-prose pre {
  margin: 0;
  padding: var(--mk-spacing-4);
  overflow-x: auto;
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-bg-subtle);
  font-size: var(--mk-font-size-sm);
  line-height: var(--mk-font-line-height-normal);
}

.ex-prose pre code {
  padding: 0;
  background-color: transparent;
  font-size: inherit;
}

.ex-prose hr {
  width: 100%;
  margin: var(--mk-spacing-2) 0;
  border: 0;
  border-top: 1px solid var(--mk-color-border);
}

/* Data tables: base.css sets tabular numerals; numbers align right. */
.ex-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-md);
}

.ex-table {
  width: 100%;
  border-collapse: collapse;
  background-color: var(--mk-color-surface);
  font-size: var(--mk-font-size-sm);
}

.ex-table caption {
  padding: var(--mk-spacing-3) var(--mk-spacing-4);
  color: var(--mk-color-text-muted);
  font-size: var(--mk-font-size-sm);
  text-align: left;
}

.ex-table th,
.ex-table td {
  padding: var(--mk-spacing-2) var(--mk-spacing-4);
  border-top: 1px solid var(--mk-color-border);
  text-align: left;
}

.ex-table thead th {
  background-color: var(--mk-color-bg-subtle);
  color: var(--mk-color-text-muted);
  font-size: var(--mk-font-size-xs);
  font-weight: var(--mk-font-weight-semibold);
  letter-spacing: var(--mk-font-letter-spacing-label);
  text-transform: uppercase;
  white-space: nowrap;
}

.ex-table .ex-num {
  text-align: right;
}

.ex-table tbody tr:hover {
  background-color: var(--mk-color-accent-subtle);
}

Cards on a subtle section

surface cards raised with shadow.raised on bg-subtle. The title is the link. Forced below it: hover, keyboard focus.

Cards on a subtle section HTML
<section class="ex-section">
  <div class="ex-card-grid">
    <article class="ex-card">
      <p class="ex-eyebrow">Article</p>
      <h3 class="ex-card-title">
        <a class="ex-card-link" href="#tokens">Design tokens across four stacks</a>
      </h3>
      <p class="ex-card-text">One palette for Angular, WordPress, Razor and static pages.</p>
    </article>
    <article class="ex-card">
      <p class="ex-eyebrow">Talk</p>
      <h3 class="ex-card-title">
        <a class="ex-card-link" href="#contrast">Contrast as a contract</a>
      </h3>
      <p class="ex-card-text">Testing WCAG ratios in CI instead of in review.</p>
    </article>
    <article class="ex-card">
      <p class="ex-eyebrow">Project</p>
      <h3 class="ex-card-title"><a class="ex-card-link" href="#cdn">A CDN for stylesheets</a></h3>
      <p class="ex-card-text">Pinned versions that never change, and an alias that does.</p>
    </article>
  </div>
</section>

Prose

Headings, links, lists, a quotation, inline code, keys and a code block.

Prose HTML
<article class="ex-prose">
  <h1>Shipping one palette to every site</h1>
  <p>
    Each site links one stylesheet. A change to a token reaches every site on the next release, and
    <a href="#versioning">semantic versioning</a> keeps breaking changes out.
  </p>
  <h2>How a release works</h2>
  <ol>
    <li>Merge the change into <code>main</code>.</li>
    <li>Push a tag such as <code>v1.5.0</code>.</li>
    <li>The workflow uploads, publishes and verifies.</li>
  </ol>
  <h3>What sites receive</h3>
  <ul>
    <li>Every new token and value change on <code>/v1/</code>.</li>
    <li>Nothing that renames or removes a token.</li>
  </ul>
  <blockquote>
    <p>Change once, reflect everywhere.</p>
  </blockquote>
  <h4>Forcing a theme</h4>
  <p>Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd>, or set it in markup:</p>
  <pre><code>&lt;html data-theme="dark"&gt;</code></pre>
  <hr />
  <p>Last updated 7 October 2026.</p>
</article>

Data table

Caption, column headers, row headers and right-aligned numbers.

Data table HTML
<div class="ex-table-wrap">
  <table class="ex-table">
    <caption>
      Specimen page views by month
    </caption>
    <thead>
      <tr>
        <th scope="col">Month</th>
        <th scope="col" class="ex-num">Views</th>
        <th scope="col" class="ex-num">Visitors</th>
        <th scope="col" class="ex-num">Change</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">July</th>
        <td class="ex-num">1,204</td>
        <td class="ex-num">811</td>
        <td class="ex-num">+4.1%</td>
      </tr>
      <tr>
        <th scope="row">August</th>
        <td class="ex-num">1,377</td>
        <td class="ex-num">902</td>
        <td class="ex-num">+11.2%</td>
      </tr>
      <tr>
        <th scope="row">September</th>
        <td class="ex-num">1,118</td>
        <td class="ex-num">760</td>
        <td class="ex-num">&minus;15.7%</td>
      </tr>
    </tbody>
  </table>
</div>

Navigation

The current page is marked with aria-current, and shown by more than colour.

Tokens used --mk-z-index-nav --mk-spacing-2 --mk-spacing-6 --mk-spacing-3 --mk-spacing-5 --mk-color-border --mk-radius-md --mk-color-surface --mk-color-text --mk-font-family-display --mk-font-weight-bold --mk-font-letter-spacing-heading --mk-spacing-1 --mk-color-text-secondary --mk-font-size-sm --mk-font-weight-medium --mk-color-border-control --mk-color-accent --mk-color-text-muted --mk-font-weight-semibold --mk-spacing-4 --mk-color-accent-subtle --mk-color-on-accent --mk-color-accent-hover --mk-color-bg-subtle --mk-z-index-toast --mk-shadow-raised

Navigation CSS
/* Top navigation: the current page is marked with aria-current, not colour
   alone; it gets the accent colour and an underline bar. */
.ex-nav {
  position: sticky;
  top: 0;
  z-index: var(--mk-z-index-nav);
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-2) var(--mk-spacing-6);
  align-items: center;
  padding: var(--mk-spacing-3) var(--mk-spacing-5);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
}

.ex-nav-brand {
  color: var(--mk-color-text);
  font-family: var(--mk-font-family-display);
  font-weight: var(--mk-font-weight-bold);
  letter-spacing: var(--mk-font-letter-spacing-heading);
  text-decoration: none;
}

.ex-nav-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-1) var(--mk-spacing-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ex-nav-link {
  display: inline-block;
  padding: var(--mk-spacing-1) 0;
  border-bottom: 2px solid transparent;
  color: var(--mk-color-text-secondary);
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-medium);
  text-decoration: none;
}

.ex-nav-link:hover {
  color: var(--mk-color-text);
  border-bottom-color: var(--mk-color-border-control);
}

.ex-nav-link[aria-current="page"] {
  border-bottom-color: var(--mk-color-accent);
  color: var(--mk-color-accent);
}

/* Breadcrumbs: separators are decoration, drawn by CSS and not read out. */
.ex-breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--mk-font-size-sm);
}

.ex-breadcrumbs li + li::before {
  content: "/";
  margin-right: var(--mk-spacing-2);
  color: var(--mk-color-text-muted);
}

.ex-breadcrumbs a {
  color: var(--mk-color-text-secondary);
}

.ex-breadcrumbs a:hover {
  color: var(--mk-color-accent);
}

.ex-breadcrumbs [aria-current="page"] {
  color: var(--mk-color-text);
  font-weight: var(--mk-font-weight-semibold);
}

/* Tabs: markup and styles only. Arrow-key behaviour is the site's script. */
.ex-tabs {
  display: flex;
  gap: var(--mk-spacing-1);
  overflow-x: auto;
  border-bottom: 1px solid var(--mk-color-border);
}

.ex-tab {
  margin-bottom: -1px;
  padding: var(--mk-spacing-2) var(--mk-spacing-4);
  border: 0;
  border-bottom: 2px solid transparent;
  background-color: transparent;
  color: var(--mk-color-text-secondary);
  font-size: var(--mk-font-size-sm);
  font-weight: var(--mk-font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
}

.ex-tab:hover {
  color: var(--mk-color-text);
  border-bottom-color: var(--mk-color-border-control);
}

.ex-tab[aria-selected="true"] {
  border-bottom-color: var(--mk-color-accent);
  color: var(--mk-color-text);
  font-weight: var(--mk-font-weight-semibold);
}

.ex-tab-panel {
  padding: var(--mk-spacing-4) 0;
  color: var(--mk-color-text-secondary);
}

/* Pagination: the current page is filled with the accent. */
.ex-pagination ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-1);
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ex-page {
  display: inline-grid;
  place-items: center;
  min-width: 2.5rem;
  height: 2.5rem;
  padding: 0 var(--mk-spacing-3);
  border: 1px solid var(--mk-color-border-control);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-sm);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.ex-page:hover {
  background-color: var(--mk-color-accent-subtle);
}

.ex-page[aria-current="page"] {
  border-color: var(--mk-color-accent);
  background-color: var(--mk-color-accent);
  color: var(--mk-color-on-accent);
  font-weight: var(--mk-font-weight-semibold);
}

.ex-page[aria-current="page"]:hover {
  border-color: var(--mk-color-accent-hover);
  background-color: var(--mk-color-accent-hover);
}

.ex-page[aria-disabled="true"] {
  border-style: dashed;
  background-color: var(--mk-color-bg-subtle);
  color: var(--mk-color-text-muted);
  pointer-events: none;
}

.ex-page-gap {
  padding: 0 var(--mk-spacing-1);
  color: var(--mk-color-text-muted);
}

/* Skip link: off screen until it receives keyboard focus. */
.ex-skip-link {
  position: absolute;
  top: var(--mk-spacing-2);
  left: var(--mk-spacing-2);
  z-index: var(--mk-z-index-toast);
  padding: var(--mk-spacing-2) var(--mk-spacing-4);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
  color: var(--mk-color-accent);
  font-weight: var(--mk-font-weight-semibold);
  transform: translateY(-200%);
}

.ex-skip-link:focus-visible {
  transform: none;
}

Top navigation

Sticky on z-index.nav. The current page gets the accent and an underline bar. Forced below it: hover, keyboard focus.

Top navigation HTML
<header class="ex-nav">
  <a class="ex-nav-brand" href="#home">Manjunath</a>
  <nav aria-label="Main">
    <ul class="ex-nav-list">
      <li><a class="ex-nav-link" href="#writing" aria-current="page">Writing</a></li>
      <li><a class="ex-nav-link" href="#projects">Projects</a></li>
      <li><a class="ex-nav-link" href="#talks">Talks</a></li>
      <li><a class="ex-nav-link" href="#about">About</a></li>
    </ul>
  </nav>
</header>

Breadcrumbs

Separators are drawn by CSS, so screen readers do not announce them.

Breadcrumbs HTML
<nav class="ex-breadcrumbs" aria-label="Breadcrumb">
  <ol>
    <li><a href="#home">Home</a></li>
    <li><a href="#writing">Writing</a></li>
    <li><span aria-current="page">Design tokens across four stacks</span></li>
  </ol>
</nav>

Tabs

Markup and styles only; arrow-key behaviour is the site's script. Forced below it: hover, keyboard focus.

Tabs HTML
<div>
  <div class="ex-tabs" role="tablist" aria-label="Install">
    <button
      class="ex-tab"
      type="button"
      role="tab"
      id="ex-tab-cdn"
      aria-selected="true"
      aria-controls="ex-panel-cdn"
    >
      CDN
    </button>
    <button
      class="ex-tab"
      type="button"
      role="tab"
      id="ex-tab-npm"
      aria-selected="false"
      aria-controls="ex-panel-npm"
      tabindex="-1"
    >
      npm
    </button>
    <button
      class="ex-tab"
      type="button"
      role="tab"
      id="ex-tab-scss"
      aria-selected="false"
      aria-controls="ex-panel-scss"
      tabindex="-1"
    >
      SCSS
    </button>
  </div>
  <div class="ex-tab-panel" role="tabpanel" id="ex-panel-cdn" aria-labelledby="ex-tab-cdn">
    Link one stylesheet from the CDN.
  </div>
</div>

Pagination

The current page is filled with the accent; an unavailable link is dashed. Forced below it: hover, keyboard focus.

Pagination HTML
<nav class="ex-pagination" aria-label="Pages">
  <ul>
    <li><a class="ex-page" aria-disabled="true">Previous</a></li>
    <li><a class="ex-page" href="#page-1" aria-current="page">1</a></li>
    <li><a class="ex-page" href="#page-2">2</a></li>
    <li><a class="ex-page" href="#page-3">3</a></li>
    <li><span class="ex-page-gap" aria-hidden="true">&hellip;</span></li>
    <li><a class="ex-page" href="#page-9">9</a></li>
    <li><a class="ex-page" href="#page-2">Next</a></li>
  </ul>
</nav>

Skip link

Hidden until keyboard focus. Press Tab inside the preview to see the live one. Forced below it: keyboard focus.

Skip link HTML
<a class="ex-skip-link" href="#main">Skip to content</a>

Overlay

Layers above the page. Modal dialogs use the top layer; other overlays use z-index.overlay.

Tokens used --mk-spacing-4 --mk-spacing-6 --mk-color-border --mk-radius-lg --mk-color-surface --mk-shadow-raised --mk-color-text --mk-spacing-3 --mk-font-size-lg --mk-color-text-secondary --mk-spacing-1 --mk-z-index-overlay --mk-radius-md --mk-spacing-2 --mk-radius-sm --mk-font-size-sm --mk-color-accent-subtle --mk-color-danger

Overlay CSS
/* Dialog: open it with showModal() so it sits in the top layer and traps
   focus. No scrim colour token exists yet, so the backdrop only blurs. */
.ex-dialog {
  width: min(28rem, calc(100% - 2 * var(--mk-spacing-4)));
  padding: var(--mk-spacing-6);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
  color: var(--mk-color-text);
}

.ex-dialog::backdrop {
  backdrop-filter: blur(2px);
}

.ex-dialog-body {
  display: grid;
  gap: var(--mk-spacing-3);
}

.ex-dialog-title {
  font-size: var(--mk-font-size-lg);
}

.ex-dialog-text {
  color: var(--mk-color-text-secondary);
}

.ex-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-3);
  justify-content: flex-end;
  margin-top: var(--mk-spacing-3);
}

/* Menu: a non-modal list under its button, above content on z-index.overlay.
   Showing and hiding it, and arrow keys, are the site's script. */
.ex-menu-anchor {
  position: relative;
  display: inline-block;
}

.ex-menu {
  position: absolute;
  top: calc(100% + var(--mk-spacing-1));
  left: 0;
  z-index: var(--mk-z-index-overlay);
  display: grid;
  min-width: 12rem;
  margin: 0;
  padding: var(--mk-spacing-1);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-md);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
  list-style: none;
}

.ex-menu-item {
  display: block;
  width: 100%;
  padding: var(--mk-spacing-2) var(--mk-spacing-3);
  border: 0;
  border-radius: var(--mk-radius-sm);
  background-color: transparent;
  color: var(--mk-color-text);
  font-size: var(--mk-font-size-sm);
  text-align: left;
  cursor: pointer;
}

.ex-menu-item:hover {
  background-color: var(--mk-color-accent-subtle);
}

/* Items touch, so the focus ring is drawn inside them. */
.ex-menu-item:focus-visible {
  outline-offset: -2px;
}

.ex-menu-item-danger {
  color: var(--mk-color-danger);
}

/* danger on accent-subtle over a dark surface is 3.98:1, below 4.5:1, so
   hover outlines the item instead of tinting it. */
.ex-menu-item-danger:hover {
  background-color: transparent;
  box-shadow: inset 0 0 0 1px var(--mk-color-danger);
}

.ex-menu-separator {
  height: 1px;
  margin: var(--mk-spacing-1) 0;
  background-color: var(--mk-color-border);
}

Dialog

Shown open in place; a site opens it with showModal(). Uses the Actions CSS for its buttons. Forced below it: hover, keyboard focus.

Dialog HTML
<dialog class="ex-dialog" open aria-labelledby="ex-dialog-title">
  <form class="ex-dialog-body" method="dialog">
    <h2 class="ex-dialog-title" id="ex-dialog-title">Delete this draft?</h2>
    <p class="ex-dialog-text">The draft and its notes are removed. This cannot be undone.</p>
    <div class="ex-dialog-actions">
      <button class="ex-button" value="cancel">Keep draft</button>
      <button class="ex-button ex-button-danger-solid" value="delete">Delete draft</button>
    </div>
  </form>
</dialog>

Menu

Shown open under its button. Uses the Actions CSS for the button. Forced below it: hover, keyboard focus.

Menu HTML
<div class="ex-menu-anchor">
  <button
    class="ex-button"
    type="button"
    aria-haspopup="menu"
    aria-expanded="true"
    aria-controls="ex-menu"
  >
    Options
  </button>
  <ul class="ex-menu" id="ex-menu" role="menu">
    <li role="none"><button class="ex-menu-item" type="button" role="menuitem">Edit</button></li>
    <li role="none">
      <button class="ex-menu-item" type="button" role="menuitem">Duplicate</button>
    </li>
    <li role="none">
      <button class="ex-menu-item" type="button" role="menuitem">Share link</button>
    </li>
    <li class="ex-menu-separator" role="separator"></li>
    <li role="none">
      <button class="ex-menu-item ex-menu-item-danger" type="button" role="menuitem">Delete</button>
    </li>
  </ul>
</div>

Page

A small site built from the tokens, at phone width and at full width. It reflows at breakpoint.sm and breakpoint.lg.

Tokens used --mk-color-bg --mk-layout-container-max --mk-layout-gutter --mk-z-index-nav --mk-color-border --mk-spacing-2 --mk-spacing-6 --mk-spacing-3 --mk-color-text --mk-font-family-display --mk-font-weight-bold --mk-spacing-1 --mk-spacing-5 --mk-font-size-sm --mk-color-text-secondary --mk-color-accent --mk-spacing-16 --mk-spacing-12 --mk-font-size-3xl --mk-font-letter-spacing-display --mk-font-line-height-tight --mk-font-size-md --mk-color-bg-subtle --mk-font-size-xl --mk-spacing-4 --mk-radius-lg --mk-color-surface --mk-shadow-raised --mk-font-size-lg --mk-spacing-8 --mk-color-text-muted

Page CSS
/* A whole page from tokens alone. Media queries cannot read custom
   properties, so they repeat the breakpoint tokens' values: 560px is
   breakpoint.sm and 1000px is breakpoint.lg (a test keeps them in step). */
.ex-site {
  min-height: 100%;
  background-color: var(--mk-color-bg);
}

.ex-container {
  width: 100%;
  max-width: var(--mk-layout-container-max);
  margin: 0 auto;
  padding: 0 var(--mk-layout-gutter);
}

.ex-site-header {
  position: sticky;
  top: 0;
  z-index: var(--mk-z-index-nav);
  border-bottom: 1px solid var(--mk-color-border);
  background-color: var(--mk-color-bg);
}

.ex-site-header .ex-container {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-2) var(--mk-spacing-6);
  align-items: center;
  justify-content: space-between;
  padding-block: var(--mk-spacing-3);
}

.ex-site-brand {
  color: var(--mk-color-text);
  font-family: var(--mk-font-family-display);
  font-weight: var(--mk-font-weight-bold);
  text-decoration: none;
}

.ex-site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-1) var(--mk-spacing-5);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--mk-font-size-sm);
}

.ex-site-nav a {
  color: var(--mk-color-text-secondary);
  text-decoration: none;
}

.ex-site-nav a:hover,
.ex-site-nav a[aria-current="page"] {
  color: var(--mk-color-accent);
}

.ex-hero {
  display: grid;
  gap: var(--mk-spacing-5);
  padding-block: var(--mk-spacing-16) var(--mk-spacing-12);
}

.ex-hero-title {
  max-width: 14ch;
  font-size: var(--mk-font-size-3xl);
  letter-spacing: var(--mk-font-letter-spacing-display);
  line-height: var(--mk-font-line-height-tight);
}

.ex-hero-lede {
  color: var(--mk-color-text-secondary);
  font-size: var(--mk-font-size-md);
}

.ex-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-3);
}

/* .mk-grid-bg comes from base.css: the drafting grid behind a section. */
.ex-band {
  padding-block: var(--mk-spacing-12);
  border-block: 1px solid var(--mk-color-border);
  background-color: var(--mk-color-bg-subtle);
}

.ex-band-title {
  margin-bottom: var(--mk-spacing-6);
  font-size: var(--mk-font-size-xl);
}

.ex-site-grid {
  display: grid;
  gap: var(--mk-spacing-4);
  grid-template-columns: minmax(0, 1fr);
}

@media (width >= 560px) {
  .ex-site-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (width >= 1000px) {
  .ex-site-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.ex-site-card {
  display: grid;
  gap: var(--mk-spacing-2);
  padding: var(--mk-spacing-5);
  border: 1px solid var(--mk-color-border);
  border-radius: var(--mk-radius-lg);
  background-color: var(--mk-color-surface);
  box-shadow: var(--mk-shadow-raised);
}

.ex-site-card h3 {
  font-size: var(--mk-font-size-lg);
}

.ex-site-card p {
  color: var(--mk-color-text-secondary);
}

.ex-site-footer {
  padding-block: var(--mk-spacing-8);
  color: var(--mk-color-text-muted);
  font-size: var(--mk-font-size-sm);
}

.ex-site-footer .ex-container {
  display: flex;
  flex-wrap: wrap;
  gap: var(--mk-spacing-3) var(--mk-spacing-6);
  justify-content: space-between;
}

.ex-site-footer-link {
  color: var(--mk-color-text-secondary);
}

Mock site

Sticky header, hero in the display face, a bg-subtle band with .mk-grid-bg, cards and a footer. Uses the Actions CSS for its buttons.

Mock site HTML
<div class="ex-site">
  <header class="ex-site-header">
    <div class="ex-container">
      <a class="ex-site-brand" href="#home">Manjunath</a>
      <nav aria-label="Main">
        <ul class="ex-site-nav">
          <li><a href="#writing" aria-current="page">Writing</a></li>
          <li><a href="#projects">Projects</a></li>
          <li><a href="#talks">Talks</a></li>
          <li><a href="#about">About</a></li>
        </ul>
      </nav>
    </div>
  </header>
  <main>
    <section class="ex-container ex-hero">
      <h1 class="ex-hero-title">Building for the web, one token at a time</h1>
      <p class="ex-hero-lede">
        Notes on design systems, accessibility and shipping small things well.
      </p>
      <div class="ex-hero-actions">
        <a class="ex-button ex-button-primary" href="#writing">Read the writing</a>
        <a class="ex-button" href="#about">About me</a>
      </div>
    </section>
    <section class="ex-band mk-grid-bg">
      <div class="ex-container">
        <h2 class="ex-band-title">Recent writing</h2>
        <div class="ex-site-grid">
          <article class="ex-site-card">
            <h3>Design tokens across four stacks</h3>
            <p>One palette for Angular, WordPress, Razor and static pages.</p>
          </article>
          <article class="ex-site-card">
            <h3>Contrast as a contract</h3>
            <p>Testing WCAG ratios in CI instead of in review.</p>
          </article>
          <article class="ex-site-card">
            <h3>A CDN for stylesheets</h3>
            <p>Pinned versions that never change, and an alias that does.</p>
          </article>
        </div>
      </div>
    </section>
  </main>
  <footer class="ex-site-footer">
    <div class="ex-container">
      <p>&copy; 2026 Manjunath</p>
      <p>
        <a class="ex-site-footer-link" href="#source">Source</a> &middot;
        <a class="ex-site-footer-link" href="#feed">Feed</a>
      </p>
    </div>
  </footer>
</div>

Not yet expressible

Patterns the current tokens cannot draw directly. The examples use the fallback shown; adding the missing token is a separate decision.

PatternMissing tokenFallback in the examples
Filled destructive button hover A hover shade of color.danger. Hover underlines the label.
Filled status badges Text colours for use on success, warning and info fills, checked at 4.5:1 against them. Outlined in the status colour, which already meets 4.5:1 on bg, bg-subtle and surface.
Tinted alert backgrounds A subtle tint per status colour, like color.accent-subtle for accent. Alerts sit on surface with a status-coloured edge, icon and title. Not planned: at 14% alpha, success and warning text fail 4.5:1 on their own tint in light mode.
Dialog backdrop A scrim colour to dim the page behind a modal dialog. The backdrop blurs the page (backdrop-filter) without dimming it. To be added when a site uses modal dialogs.
Disabled controls A disabled foreground and background pair. bg-subtle fill, text-muted label and a dashed border-control outline. Not planned: WCAG 2.2 exempts disabled controls from contrast.

Type scale

TokenValueSample
font.size.2xl clamp(34px, 4vw, 64px) The quick brown fox
font.size.3xl clamp(42px, 5.6vw, 98px) The quick brown fox
font.size.base 17px The quick brown fox
font.size.display clamp(50px, 8.1vw, 148px) The quick brown fox
font.size.lg 21px The quick brown fox
font.size.md 18px The quick brown fox
font.size.sm 14px The quick brown fox
font.size.xl clamp(28px, 3vw, 40px) The quick brown fox
font.size.xs 12px The quick brown fox

Spacing

TokenValueSample
spacing.1 4px
spacing.10 40px
spacing.12 48px
spacing.16 64px
spacing.2 8px
spacing.20 80px
spacing.24 96px
spacing.3 12px
spacing.32 128px
spacing.4 16px
spacing.5 20px
spacing.6 24px
spacing.8 32px

Radius

TokenValueSample
radius.lg 12px
radius.md 6px
radius.pill 999px
radius.sm 4px
radius.xl 16px

Shadow review (D7)

shadow.raised = 0 16px 40px -20px #00000066

Card on color.bg

Raised depth on the page background.

Card on color.bg-subtle

The same token on the subtle background. Switch theme to review both modes.

Motion

motion.ease = cubic-bezier(0.2, 0.7, 0.1, 1) (hover samples)

TokenValueSample
motion.duration.base 300ms
motion.duration.fast 150ms
motion.duration.slow 500ms
motion.duration.slower 900ms