Facets

Style in Milda is a small, abstract paint vocabulary, the facets, each bound to a token and keyed by states a behaviour emits and conditions over props. There are no CSS properties in the model and no per-platform style code anywhere: divergence lives entirely in how tokens resolve.

#What a facet is

A facet is a slot on a part: a what to paint, never a how. “This part's fill is the surface token” is a statement every platform can honour; background-color is not. On the surface a facet is a word followed by its values: a dotted value is a token reference, a number is a literal, a bare word is an intent keyword.

milda
part Button: container {
  fill accent
  ink on-accent
  border line hairline solid
  corner radius.sm
  inset space.sm space.lg
  text body
  weight weight.medium
  elevation shadow.card
  opacity 90
}

#The vocabulary

The prelude defines 25 facets. For each: what it covers, and where the targets diverge in realizing it.

FacetCoversDivergence
fillsurface/background-
inktext + icon foreground-
borderedge: color + width (hairline|thin|thick) + styleiOS often overlay-stroke
border.toptop edge (physical side)-
border.rightright edge (physical side)-
border.bottombottom edge (physical side)-
border.leftleft edge (physical side)-
border.inline-startleading edge (logical, writing-direction)-
border.inline-endtrailing edge (logical, writing-direction)-
border.block-startblock-start edge (logical)-
border.block-endblock-end edge (logical)-
cornercorner radius (token role)-
insetinternal padding (block/inline)-
gapspacing between children-
texttypography role (body|caption|title…) - bundles size/weight/line/trackingiOS Dynamic Type = OS-resolved role
textClamptruncate to N lines + ellipsisweb -webkit-line-clamp · .lineLimit · maxLines+overflow
textWrapwrap | nowrap | break-
weightfont-weight (typography axis §3, split out of the `text` role bundle)web font-weight · .fontWeight · TextStyle.fontWeight
fontfont-family (typography axis §3)web font-family · UIFont · Typeface
slantfont-style: normal | italic | oblique (typography axis §3)-
elevationdepthweb box-shadow · Android tonal-elevation · iOS none/sheet
overlaystate layer (Material hover/press tint)Material-specific; web ≈ bg shift
opacitytransparency-
ringfocus indicatordesktop ring · mobile none · tv strong highlight
backdropscrim / blur behind layeriOS material/vibrancy
  • A facet name may carry one sub-key: text.clamp (truncate to N lines), text.wrap, border.inline-start (one logical edge) or border.top (one physical edge).
  • border takes up to three values in any order: a colour token, a width token (a border-typed token such as a hairline), and solid | dashed | dotted.
  • inset takes one or two tokens: all sides, or block then inline. One axis is inset.inline or inset.block, and one edge is inset.inline-start, inset.inline-end, inset.block-start or inset.block-end. The edges are logical, so the start edge is the leading side in every writing direction, and the most specific statement wins on its edge.
  • text takes a typography token or a font-size token; font, weight and slant override one axis of the role.

#Facets bind to tokens

A facet's value is a token reference. Tokens are typed and layered, and a token can answer per context; binding a facet to a token rather than a literal is what makes a restyle a foundations change and a dark scheme one more alias. A facet cannot be context-aliased itself (fill alias by … is an error): point it at a token and put the alias on the token. The mechanism, including platform divergence through elevation, ring and backdrop tokens that alias by platform, is on Tokens & context.

A facet value that resolves as a token takes the token; a bare number falls back to a literal only when no token claims it. So opacity 50 and text.weight 600 work as literals, while a fill with no token behind it is reported, not guessed.

#States and conditions

Paint changes under two kinds of condition, and the language has one word for each. is a behaviour state: hovered, pressed, focus-visible, selected, open, checked, and the rest of the vocabulary the atoms emit, plus the ambient gates (disabled, readonly) and collection positions (first, last, odd, even). The list is closed: any other word is reported, and state hover is told it meant hovered. is a predicate with no state involved: a prop value, a context, an ancestor's state. The two combine.

milda
part option: item {
  fill surface
  state active { fill surface.raised }
  state selected { ink accent }
  state disabled { opacity 50 }
}
milda
part Button: container {
  fill accent
  when variant = Secondary {
    fill surface
  }
  state hovered when variant = Secondary {
    fill accent-subtle
  }
  when context ColorScheme = Dark and state focused {
    ring on-accent
  }
}
part icon: icon {
  ink text
  when ancestor Button hovered {
    ink accent
  }
}
  • Predicates combine with and, or, not and parentheses, compare with =, !=, <, contains, and take four kinds of leaf: a prop, a , a context, and an ancestor <part> <state>, which styles a descendant on an ancestor's state.
  • A state can come from a prop: bind state disabled disabled puts the part into the state while the prop is true.
  • Layout can change too: a or block may carry a layout { } block beside its facets.

#Three layers

The spec definesA .milda file can sayThe React target does
The facet vocabulary above, with one sub-key per facet, token-valued, keyed by behaviour states; divergent facets as abstract token types aliased by platform.Every facet in the table, with the value forms above; and blocks with the full predicate grammar; a block of transitions.Maps each facet to CSS declarations whose values are the theme's custom properties; a prop condition to a data attribute and selector; a behaviour state to the pseudo-class or data attribute its realization uses; an ancestor predicate to a descendant selector. The Figma target maps the same facets to Variable bindings; the facet semantics are stated once and each target's coverage of every slot is total by construction.

#Motion

A motion { transition fill ink duration fast easing standard } block names the facets that animate between states and the tokens that time them: a duration and an easing (Values & literals). Motion adds no state; the behaviour states are its anchors.

Why this design holds up
Every choice here - roles over values, tokens over literals, states over pseudo-classes, aliasing over style blocks - is the same choice: push the platform-specific decision to the latest possible moment, token resolution or generation, so the authored model stays portable.

Structure and spacing intents live one layer over: Layout.

These words, in the reference

  • A paint facet: an abstract slot such as "background" or "text colour", pointed at a token.
  • Paint for an interaction state.
  • Paint that applies while a condition holds, with no interaction state involved: the variants of a component.
  • How the part animates between its states.
  • A condition.

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

See it built in the tour4. Variants and states10. Type, motion and computed values