Values & literals

In Milda a value knows what it is. Colours are space-aware functions, lengths carry a unit, durations are times, and records have named fields. Because every literal types itself, the language can tell a colour from a length from a time, and flag a value that lands in the wrong slot before anything is generated.

Most of the time you name values as tokens and reference those tokens everywhere else. Literals are how you define a token, and the occasional one-off. This page is the vocabulary of literals themselves.

#Colours are functions, not strings

A leading # starts a comment, so a colour cannot be a #rrggbb string. Colours are written as functions that name their colour space, so the value is unambiguous rather than a string a target has to guess at:

milda
hex(ffffff)          # sRGB, RRGGBB; also 3, 4 or 8 digits
hex(ffffffcc)        # RRGGBBAA - the last byte is alpha
rgb(255 0 0)         # sRGB, 0-255 channels
srgb(1 0 0)          # sRGB, 0-1 channels
srgb(1 1 1 0.8)      # a trailing number is alpha
p3(1 0 0)            # display-p3, a wider gamut
oklch(0.62 0.19 256) # OKLCH: lightness, chroma, hue
hsl(210 1 0.5)       # HSL
transparent          # no colour at all

The spaces are hex, rgb, srgb, p3, oklch and hsl (rgba and hsla are read as the same). They round-trip exactly: the parser and the printer are symmetric, so the colour you write is the colour that comes back out.

#Lengths carry their units

A number with a unit is a length, and the unit says what kind. Two kinds exist, and both are platform-neutral:

milda
8px           # a density-independent length; the canonical spelling
scaled(1.25)  # a length relative to the reader's text size
0px           # zero still carries a unit
pill          # a radius that fully rounds the shorter side

px is not a CSS pixel: it is the unit Android calls dp, and every target converts it. The reader also accepts dp as a spelling of px, and rem and sp as spellings of scaled(), for authors arriving from the web and from Android; the printer writes the canonical forms. A length type (spacing, radius, size, border, font-size, letter-spacing) rejects a bare number: the unit is what makes it a length.

#Durations are times

Time units make a duration, used by motion tokens and transitions:

milda
token fast : duration = 120ms
token slow : duration = 0.4s

#Numbers that are not lengths

Three types take a bare number, and the type word is what keeps the number from being read as a length: a font weight, a ratio (a line height is a multiple of the text size, never a length), and an opacity.

milda
token weight/medium : font-weight = 500
token leading/normal : ratio = 1.5
token dim : opacity = 0.5

Older files spell the ratio type line-height or number. Both are still read; ratio is what Milda writes, and it earned its own surface version because a reader that does not know the word would otherwise read 1.5 as 1.5px.

#Records: a font family and a text style

Two token types are records, written as fields in braces. A font-family says where the family comes from, which is what a generator needs to decide whether to link a stylesheet, write an @font-face or do nothing. A typography token bundles the axes of a text style, and its family may be a font-family token's path, so the style stays tied to a family that can differ per context:

milda
token font/sans : font-family = { family: "Inter"; fallback: sans-serif; source: google; license: "OFL-1.1" }
token body : typography = { family: font.sans; size: 15px; weight: 400; line-height: 1.5; letter-spacing: 0px }

The reading rule for a field that takes either: a structure is the value, a bare path is the token. A part paints with the whole style through the text facet and overrides one axis with font, weight or slant; see Facets.

#Computed values

A token can be derived from another, live, with steps that follow its type: lighten, darken, saturate, desaturate, alpha, rotate and mix for a colour, in a chosen colour space; scale, offset and round for a length. When the base changes, the result follows. Where a computed token sits among the layers is on Tokens & context.

milda
token accent-hover : color = compute accent in oklch { darken 10% }
token space/lg : spacing = compute space.sm { scale 2 }

#Literals are self-typing

Because each literal carries its own kind, the language checks it against the token's declared type. A colour in a length slot, or a length where a colour belongs, is caught before anything is generated:

milda
token bad : color = 8px   # error: a length literal in a colour slot
This powers the editors
Self-typing is what lets your editor flag a mistyped value inline, and colour a hex(...) differently from a keyword, without running a generator.

#Opaque values and the escape hatch

Two types have no neutral literal yet and are written as an opaque string with : a shadow and an easing curve. The reader carries the string and each target renders it as it can.

milda
token shadow/card : elevation = raw "0 4px 12px -2px rgba(15,23,42,0.12)"
token standard : easing = raw "cubic-bezier(0.2, 0, 0, 1)"

When a value genuinely cannot be said in the language at all, carries a platform string verbatim and marks the token as partial: a target that cannot read the string has nothing to fall back on, so the note says why. It is a deliberate, visible escape hatch, never the default.

milda
token tint : color = escape "color-mix(in srgb, red 40%, blue)" note "web only, until compute can mix in srgb"

These words, in the reference

  • A value written as itself.
  • An explicit typed value.
  • A platform-specific value the language cannot express, carried verbatim.

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

See it built in the tour1. A file and a token10. Type, motion and computed values