The .milda file

One text file carries a whole design system - foundations, components, the library's organisation, the demo scenes, the asset library and the documentation - and reads back into the editor it came from, or into any reader that implements the grammar. This page is what the file promises: what it carries, what it deliberately leaves out, and how its references resolve.

Everything here is the reader's contract, not a description of our exporter. If a field of a design system is not in the “carried” list and not in the “left out” list with a reason, that is a bug in the format.

#What it carries

A file is a sequence of declarations, in any order. The header declares the surface version; everything else is the system:

milda
milda 0.4                     # the surface version - see "Versioning" below
prelude 0.0.2                 # the design-system vocabulary (archetypes, atoms, facets)

meta { name "Acme DS"; version "2.1.0" }

context-group ColorScheme { Light Dark }

foundations {
  layer Base {
    token palette/blue/500 : color = hex(3b82f6) "Our accent, at rest."
    token space/2 : spacing = 8px
    token fast : duration = 150ms
  }
}

type Tone = enum(info, danger)

group Forms "Things you type into."

component Field "A labelled text input." {
  archetype TextInput
  group Forms
  props { label : string; value : string; disabled : boolean = false }
  events { change }

  part root { kind container; layout { stack column; gap space.2 } }
  part root.label { kind text; content bind label; ink palette.blue.500 }
  part root.input {
    kind input
    bind value value
    emits { change on change payload bind value }
    a11y { labelled-by root.label }
  }
}

example "Field in a form" for Field { demo { open : boolean = false } part root { kind container } }

assets { svg "logo" { mime "image/svg+xml"; view-box "0 0 24 24"; url "https://…" } }

docs {
  page "Fields" in Guides {
    heading "Fields" anchor fields
    component Field
    component-props Field
  }
}
  • foundations - context groups, layers, token groups, every token with its type, value and description.
  • shared types- your type declarations (the language's built-ins are implicit).
  • components - archetype, description, contract (props with defaults, bindings and annotations, events), authored behaviour composition, and the whole anatomy.
  • every part - its path in the tree, kind, provenance, presence, slot descriptor, text, accessible naming, event emissions, state and value bindings, value type, repetition, cell role, control and group realization, presentation intent, media provenance and alt text, link destination, facets, layout, conditional state rules, transitions, nested instances, free behaviours, foreign content, demo actions, and the per-archetype configuration surfaces.
  • the library- component groups (nestable, with their prose) and each component's membership.
  • examples - demo scenes: what they demonstrate, their own state variables, their node tree.
  • assets- the library: folders, each asset's identity and metadata, and where its bytes are.
  • docs - site copy, footer, pages with their section and nesting, prose, and every transclusion.
  • output settings- the emission idiom and the date/time/colour representations. These are the generated components' public API, so a file without them describes a different contract than the one that was published.

#What it leaves out, and why

Five things, each for the same kind of reason: they belong to a workspace rather than to the system.

  • the project / document id - the file is portable because it is not tied to one workspace.
  • activity history - a record about the system, not the system.
  • release history and contract digests- the project's record of what it shipped.
  • publishing config, connections, credentials - where bytes go is not what the system is.
  • a part's platform element - div / span / svg would make the file a DOM description. The neutral kind is carried, and each target derives its element from it.

#Everything is addressed by name

No reference in a .milda file points at an internal id. Every one names something the file itself declares, which is what lets a reader resolve it without our database:

  • a part(an ancestor predicate, accessible naming, a behaviour's target) - its dotted part path, root.surface.label
  • a component (an instance, a docs block) - its declaration identifier
  • an event (an emission) - its declared name
  • a token - its path, palette.blue.500
  • a component group or asset folder - its slash path, Forms/Text
  • a docs page (as a parent) - its title
  • an example or an asset - its name
A part's path is the tree
root.surface.label is the child label of the child surface of root. The prefix IS the parent - the path is the only place nesting is recorded, which is why a part is declared with a path rather than a bare name.

One consequence worth knowing when you read a file: a declaration name is an identifier, because references resolve against it. Where the authored name is free text - a component called “Color picker” - the identifier is derived and the spelling you typed travels in meta { name "Color picker" }.

#What a part can say

A part block is not only paint. Each statement is a real keyword, so the file reads as a description of the component rather than as serialised state:

milda
part root.input {
  kind input                       # what it IS - neutral; the element is the target's decision
  origin author                    # provenance: archetype-born, or you added it
  locked
  present when not disabled        # renders only when the predicate holds

  content bind label               # or: content "Submit", or a block with when … -> … legs
  a11y { labelled-by root.label; described-by root.hint }
  emits { change on change payload bind value }
  bind value value                 # this part's value is this prop
  bind state disabled disabled     # a UI state driven by a prop
  value string
  repeat items as item             # or: repeat static ["a", "b"] as item
  cell header col
  control checkbox                 # checkbox | switch
  presentation accessible-name
  media img
  source bind src sample url "https://…"
  alt "A picture"
  destination bind href

  fill palette.blue.500            # paint, as before
  layout { stack row; gap space.2 }
  state hovered { fill palette.blue.600 }
  when disabled { opacity dim }

  motion { transition fill ink duration fast easing standard }
  instance Chip { label = "New"; tone = bind value }
  behavior focus-trap { return-focus-on-close true; initial-focus-node-id root.input }
  calendar { selection-mode range; week-starts-on 1 }
  severity danger
  foreign web-react "Keyboard hint" "<kbd>Cmd K</kbd>"
}
It is `content`, not `text`
text is already a paint facet - text mdsets the font size. A keyword whose meaning depended on whether its argument was a string or a word would read fine and parse wrong, so a part's text is content.

#Foreign content is quarantined

foreign carries code written for one specific target. It is in the file because silently dropping code you wrote is worse than carrying it - but it is genuinely not portable, and the target tag is what makes that honest:

  • A reader that does not implement <target> must skip the block. It must not interpret, translate or execute it, and it should surface that the component carries non-portable content.
  • Milda's own reader warns when it meets a target it cannot read, so an unsupported snippet is reported rather than silently ignored.

#Where the asset bytes are

An asset declaration carries its identity and metadata plus where the bytes are, and that is the one choice the format offers. Both are the same grammar, so a reader implements one path:

milda
assets {
  folder Brand/Icons

  # Bytes beside the file - the text stays diffable, which is what a pull request needs.
  svg "logo" in Brand/Icons { mime "image/svg+xml"; url "https://…/logo.svg" }

  # Bytes IN the file - it then needs nothing else to be complete.
  svg "mark" { mime "image/svg+xml"; data "data:image/svg+xml;base64,…" }
}

The Studio offers both: Download bundle writes the .milda plus an assets/ directory (with a manifest that also names anything it could not include), and Download self-contained inlines every asset as a data: URI. The bundle is the one to review in a pull request; the self-contained file is the one to hand to someone.

#Versioning

The header declares two versions, and they answer different questions. prelude is the design-system vocabulary - archetypes, atoms, facets. milda is the surface version, and it is what tells you whether a file omits something because the writer predated it or because the system does not have it: a milda 0.1 file has no motion statement because motion had no syntax yet, while a milda 0.4 file without one has no motion. The surface version is bumped whenever the grammar gains capability, which is why no third version number is needed.

0.4 is the first version that breaks rather than adds. A name followed by its type now has one spelling everywhere - the colon - so a props block is written size : Size and a demo block open : boolean, exactly as token brand : color and field : string already were. The colon-less form those two blocks used to accept is an error.

If you are implementing a reader: accept a file whose milda version you do not know, parse what you recognise, and report the rest rather than failing. The grammar is additive and every statement is self-delimiting.

#Reading further

  • The grammar itself - packages/milda/spec/GRAMMAR.ebnf - and the contract in prose, packages/milda/spec/FORMAT.md.
  • Values & literals for what goes on the right-hand side of a token, and Facets & tokens for the paint vocabulary.
  • The authoring walkthrough for the same model seen from the Studio.