Lesson 2 of 14

Layers and context

Base tokens hold the literals; semantic tokens alias them, per colour scheme.

#The idea

Components will never paint with violet/600 directly. They paint with roles: the accent, the surface, the text colour. Roles live in a second layer that points at the first, and a role can point somewhere different per context, so a dark scheme is a second set of arrows rather than a second system.

#What this lesson adds

A context group, then two layers of tokens:

milda
context-group ColorScheme { Light Dark }
milda
token accent : color = alias by ColorScheme { default -> violet.600; Dark -> violet.500 }
token accent-hover : color = alias by ColorScheme { default -> violet.700; Dark -> violet.600 }
token accent-subtle : color = alias by ColorScheme { default -> violet.100; Dark -> violet.900 }
token on-accent : color = alias by ColorScheme { default -> white }
token surface : color = alias by ColorScheme { default -> white; Dark -> gray.900 }
token text : color = alias by ColorScheme { default -> gray.900; Dark -> gray.50 }
token text-muted : color = alias by ColorScheme { default -> gray.500 }
token line : color = alias by ColorScheme { default -> gray.200; Dark -> gray.800 }
token success : color = alias by ColorScheme { default -> green.500 }
token warning : color = alias by ColorScheme { default -> amber.500 }

#What to notice

  • A layer is reading order. layer Base holds the literals; layer Semantic holds roles that alias them. Resolution walks down: accent asks violet.600, which is a literal, and stops.
  • A context group is a dimension. ColorScheme has two contexts, and alias by ColorScheme { default -> violet.600; Dark -> violet.500 } answers per context. is the fallback for every context the alias does not name, so Light never has to be spelled out.
  • Dimensions chain, they do not multiply. Add a Density group later and a spacing token aliases by it; a colour token still aliases by scheme alone. Two dimensions never become a dark+compact key anywhere.
  • Not only colours. The same layer holds spacing, radii, a border width and type sizes. is a radius that fully rounds the shorter side: an intent, not a large number.

#What the generator makes of it

The theme now has a block per context: :root carries the defaults, and [data-color-scheme="dark"] re-points every token that answers differently in the dark. Switching an app to dark is setting one attribute on an ancestor.

#Try it

Switch the Playground's preview to dark and open the accent token: the panel shows which arrow is live. Then give on-accent a Dark slot pointing at gray.900 and watch what changes and what does not.

Words in this lesson

  • A dimension a token can answer differently along: colour scheme, density, platform, brand.
  • A named layer of tokens, such as primitives and then the semantic tokens that alias them.
  • A token whose value depends on a context.

Open a word for its entry, or read them all in the reference.

#The file at the end of this lesson

milda
milda 0.10
prelude 0.0.2

meta { name "Acme" }

context-group ColorScheme { Light Dark }

foundations {
  layer Base {
    token violet/100 : color = hex(ede9fe)
    token violet/500 : color = hex(8b5cf6)
    token violet/600 : color = hex(7c3aed)
    token violet/700 : color = hex(6d28d9)
    token violet/900 : color = hex(4c1d95)
    token gray/50 : color = hex(f8fafc)
    token gray/200 : color = hex(e2e8f0)
    token gray/500 : color = hex(64748b)
    token gray/800 : color = hex(1e293b)
    token gray/900 : color = hex(0f172a)
    token green/500 : color = hex(22c55e)
    token amber/500 : color = hex(f59e0b)
    token white : color = hex(ffffff)
  }

  layer Semantic {
    token accent : color = alias by ColorScheme { default -> violet.600; Dark -> violet.500 }
    token accent-hover : color = alias by ColorScheme { default -> violet.700; Dark -> violet.600 }
    token accent-subtle : color = alias by ColorScheme { default -> violet.100; Dark -> violet.900 }
    token on-accent : color = alias by ColorScheme { default -> white }
    token surface : color = alias by ColorScheme { default -> white; Dark -> gray.900 }
    token text : color = alias by ColorScheme { default -> gray.900; Dark -> gray.50 }
    token text-muted : color = alias by ColorScheme { default -> gray.500 }
    token line : color = alias by ColorScheme { default -> gray.200; Dark -> gray.800 }
    token success : color = alias by ColorScheme { default -> green.500 }
    token warning : color = alias by ColorScheme { default -> amber.500 }

    token space/xs : spacing = 4px
    token space/sm : spacing = 8px
    token space/md : spacing = 12px
    token space/lg : spacing = 16px
    token space/xl : spacing = 24px

    token radius/sm : radius = 6px
    token radius/lg : radius = 12px
    token radius/pill : radius = pill

    token hairline : border = 1px

    token size/sm : font-size = 13px
    token size/md : font-size = 15px
    token size/lg : font-size = 18px
    token weight/medium : font-weight = 500
    token weight/bold : font-weight = 700
  }
}