Thinking in Milda
Every design decision in the language follows from a handful of rules. Learn these ten and the rest of the docs reads as consequences rather than as facts to memorise. Each rule comes with the few lines of .milda that show it.
#1. The system is the source. Code is one of its outputs.
One document describes the system, and everything else is produced from it: the React package, the Figma file, the docs site, the files an agent reads. You never edit an output; you change the document and regenerate. This is what keeps four surfaces from drifting into four systems, and it is why the generated code can be yours without strings: it carries nothing the document does not already say.
milda 0.10 prelude 0.0.2 meta { name "Acme" } context-group ColorScheme { Light Dark } foundations { layer Base { token violet/600 : color = hex(7c3aed) token violet/500 : color = hex(8b5cf6) token white : color = hex(ffffff) token gray/900 : color = hex(0f172a) } layer Semantic { token accent : color = alias by ColorScheme { default -> violet.600; Dark -> violet.500 } token on-accent : color = alias by ColorScheme { default -> white } token surface : color = alias by ColorScheme { default -> white; Dark -> gray.900 } token space/sm : spacing = 8px token space/lg : spacing = 16px token radius/sm : radius = 6px } } component Button "The primary action." { archetype Button props { label : string = "Get started" disabled : boolean = false } events { click } part Button: container { emits { click on activate } bind state disabled disabled layout { stack row; gap space.sm; align center } fill accent ink on-accent corner radius.sm inset space.sm space.lg state hovered { fill violet.500 } state disabled { opacity 50 } } part label: text { slot of content default content bind label } }
#2. Say what a thing is, never what it is made of.
A part has a kind: container, text, input, icon, control. It never has a tag. div, UIView and a Figma frame are three answers to the same question, and the question is the target's to answer. The same holds for everything platform-shaped: there is no CSS property, no event name of any framework, no pseudo-class anywhere in the file.
part Field: container { layout { stack column; gap space.sm } } part Field.label: text { content "Email" } part Field.control: input { value string } part Field.icon: icon { present when icon }
The payoff is that a target can be added without the document changing, and the cost is a vocabulary that is smaller than any one platform's. Where the small vocabulary cannot say something, rule 9 applies.
#3. A value has a name, and the name has a layer.
Components never paint with a literal. A facet points at a token, the token lives in a layer, and a semantic token aliases a base one. A restyle is then a change in foundations rather than a sweep through components, and a dark scheme is one more alias rather than a second stylesheet.
context-group ColorScheme { Light Dark } foundations { layer Base { token violet/600 : color = hex(7c3aed) token violet/500 : color = hex(8b5cf6) } layer Semantic { token accent : color = alias by ColorScheme { default -> violet.600; Dark -> violet.500 } } } component Button { archetype Button part Button: container { fill accent } }
#4. Context is a dimension, not a copy.
Light and dark, compact and comfortable, web and iOS, two brands: each is a context group, and a token answers per context with an alias. Several active dimensions resolve by chaining single-dimension aliases through the layers, never by a composite key like web+dark, which would explode.
A prop can be typed by a context group. That one rule is what variant, size and a per-instance accent colour are: the dimension's value comes from the instance instead of from the environment, and the same token aliasing does the rest. There is no conditional style logic to write.
context-group Variant { Primary Secondary } foundations { token accent : color = hex(7c3aed) token surface : color = hex(ffffff) token button/fill : color = alias by Variant { Primary -> accent; Secondary -> surface; default -> accent } } component Button { archetype Button props { variant : context Variant = Primary } part Button: container { fill button.fill } }
#5. A state comes from a behaviour, not from a platform.
, , selected, open: each is emitted by a behaviour, a formal state machine the archetype composes. Paint keys on those states. It never keys on :hover, because a wheel on iOS has no hover and a screen reader has no pointer, and the same state active block must mean the same highlight on all three.
part option: item { fill surface state active { fill surface.raised } state selected { ink accent } state disabled { opacity 50 } }
#6. The contract is the identity.
A component's props and events are what consumers code against, and they are what makes it the component it is. A select that is a popover listbox on the web and a spinning wheel on iOS is one component, because “one of a known set” in and a change event out is one contract. The contract is also what versioning reads: a release diffs it against the previous release and bumps the version by what changed, so a breaking change is caught by the compiler, not by a consumer.
archetype SingleSelect props { options : array<string> required value : string bind selected placeholder : string = "Choose" } events { change from S.change }
#7. Build from what exists: archetypes and instances.
A component starts from an archetype, a blueprint from the prelude with a correct contract, a published anatomy and the behaviours already composed. Pick BooleanInput and the toggle, the keyboard handling and the accessible naming arrive with it; your work is the contract, the tokens and the states. Larger things are built from smaller ones the same way: a part can be an instance of another component, with props fixed or passed through.
component Checkbox { archetype BooleanInput props { checked : boolean = false label : string required } part Checkbox: container { layout { stack row; gap space.sm; align center } } part label: text { content bind label } }
component Badge { archetype Badge props { label : string = "New"; tone : enum(neutral, success) = neutral } part Badge: container { fill accent-subtle ink accent } part label: text { content bind label } } component Card { props { tone : enum(neutral, success) = neutral } part Card: container { layout { stack column; gap space.md } } part Card.Status: instance { instance Badge { label = "Beta" tone = bind tone } } part Card.Body: text { slot of content default } }
#8. The file keeps nothing it cannot explain.
Every reference in a .milda file is by name: a part by its path, a token by its path, a component by its identifier. Nothing points at a database id, so a file resolves on its own, and a reader that is not ours can read it. Reading a file and writing it back gives the same document, and that round trip is a tested fixed point. A comment is the one thing that does not survive; anything that should has a statement of its own.
part label: text { content "Email" } part hint: text { content "We never share it." } part control: input { a11y { labelled-by label described-by hint } }
#9. Extend through seams, never by patching output.
No vocabulary covers everything. When the model cannot say what you need, the answer is a seam declared in the document: a slot where consumers put their own content, a per-target option at release time, or, as a last resort, foreign content that carries one target's code in quarantine, tagged with the target so every other reader skips it honestly. Editing generated code is never the answer: the next release overwrites it, and nothing else learns what you changed.
part chart: foreign { foreign web-react "Sparkline" "<Sparkline data={props.points} />" }
#10. Say how it is meant to be used.
A system that only lists what it offers leaves the hardest part, composition, to be rediscovered by every consumer. Milda carries that knowledge too, in two layers. An interface constraint is decided from one instance and lives in the props block, where the generated types can enforce it. A usage rule is decided over how instances are put together, and ships to agents and people with its reason.
context-group Variant { Primary Secondary } component Button { archetype Button props { variant : context Variant = Secondary loading : boolean = false loadingLabel : string required when loading "Read out while the spinner replaces the label." } } rule one-primary-action error { max 1 Button where variant = Primary within archetype Form because "A form has one way forward." }
Interface constraints are typed by the React target; usage rules ship in AGENTS.md, DESIGN.md and design-system.json and are edited in the Studio.proposal 0052
Now see the rules at work in order: the tour builds one system a lesson at a time.