Que fait milda ?
Milda est un compilateur avec interface graphique pour les systèmes de design. Un fichier .milda est le système de design :
foundations (jetons), composants, règles d'usage, documentation et assets. Les packages React, les bibliothèques Figma,
les sites de documentation et les fichiers lisibles par agent sont générés à partir de ce fichier. Vous ne modifiez jamais une sortie ; vous modifiez le fichier et régénérez.
Cette compétence cible la surface de langage 0.10 avec le prélude 0.0.2. Chaque fichier que vous écrivez commence par cet en-tête.
La boucle : écrire, vérifier, corriger
Ne dites jamais à l'utilisateur qu'un fichier .milda est valide tant que le lecteur n'est pas d'accord. Après chaque modification, exécutez :
npx -y -p @mildastudio/milda-lsp milda-check --strict chemin/vers/systeme/
Donnez-lui le répertoire, ou tous les fichiers du système ensemble, car les jetons déclarés dans un fichier
résolvent des références dans un autre. Il affiche chemin:ligne:col sévérité [code] message, les mêmes
diagnostics que les extensions d'éditeur montrent, et il se termine avec un code non nul en cas d'erreur (ou, avec --strict, en cas
d'avertissement). Corrigez ce qu'il signale et exécutez-le à nouveau jusqu'à ce qu'il affiche 0 errors, 0 warnings. Un avertissement n'est pas
négligeable : unknown facet "background" skipped signifie que cette ligne n'a rien fait.
Pour montrer le résultat à l'utilisateur, le playground sur https://play.milda.dev rend un fichier collé en direct, sans compte.
Penser en Milda : dix règles
La plupart des erreurs viennent de l'écriture de CSS, d'un arbre DOM ou d'un composant React dans la syntaxe de Milda. Ces règles sont ce que la syntaxe existe pour exprimer.
- Le système est la source ; le code est une sortie. Tout ce qui est façonné par la plateforme est décidé au moment de la génération, donc un fichier ne contient jamais une propriété CSS, une balise, un nom d'événement d'un framework ou une pseudo-classe.
- Dites ce qu'est une chose, jamais de quoi elle est faite. Une partie a un kind neutre (
container,text,input,icon,control, ...), jamaisdivoubutton. Chaque cible choisit son propre élément. - Une valeur a un nom, et le nom a une couche. Les composants ne peignent jamais avec des littéraux. Une facette
pointe vers un jeton ; une couche
Semanticalias une coucheBasede littéraux. Un restylage est un changement des foundations. - Le contexte est une dimension, pas une copie. Mode sombre, densité, marque : chacun est un
context-group, et un jeton répond par contexte avecalias by Group { ... }. Une prop typée par un groupe de contexte (unevariant) utilise le même mécanisme par instance. - Un état vient d'un comportement.
hovered,pressed,focus-visible,disabled,open,selectedsont émis par les comportements de l'archétype. Stylisez-les avecstate hovered { }, jamais avec quelque chose de spécifique à un pointeur ou une plateforme. La liste est fermée (reference/vocabulary.md), et le lecteur signale tout autre mot :state hoverest rapporté avec l'orthographe qu'il voulait dire. - Le contrat est l'identité.
propseteventssont ce contre quoi les consommateurs codent et ce que le versionnement différencie. Nommez-les pour leur sens (variant,checkedChange), pas pour une plateforme. - Construisez à partir de ce qui existe. Commencez un composant à partir d'un
archetypedans le prélude : il apporte la forme du contrat, les parties requises et les comportements (clavier, ARIA, focus). Composez des composants plus grands à partir de plus petits avec des partiesinstance. - Chaque référence est par nom. Parties par chemin pointé (
Header.Title), jetons par chemin (space.smpourspace/sm), composants par identifiant. Pas d'identifiants. Les commentaires (#) sont la seule chose qui ne survit pas à un aller-retour. - Étendez à travers des points de couture, jamais en modifiant la sortie. Lorsque le vocabulaire ne peut pas dire quelque chose,
utilisez un
slot, une option par cible, ou en dernier recours un contenuforeignmarqué avec sa cible. Ne suggérez jamais de modifier le code généré. - Dites comment cela est censé être utilisé. Mettez les contraintes d'interface (
required,required when,one-of) dansprops, et les règles de composition (rule ... { never X within Y }) au niveau supérieur, chacune avec sa raison.
Un fichier, de haut en bas
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 white : color = hex(ffffff)
token gray/900 : color = hex(0f172a)
}
layer Semantic {
token accent : color = alias by ColorScheme { default -> violet.600 }
token on-accent : color = alias by ColorScheme { default -> white; Dark -> gray.900 }
token space/sm : spacing = 8px
token radius/sm : radius = 6px
}
}
component Button "L'action principale." {
archetype Button
props {
label : string = "Get started"
}
events {
click
}
part Button: container {
emits { click on activate }
layout { stack row; gap space.sm; align center }
fill accent
ink on-accent
corner radius.sm
inset space.sm
state focus-visible {
ring accent
}
}
part label: text {
content bind label
}
}
Ordre : en-tête, meta, context-groups, groups (sections de bibliothèque), foundations, types,
components, rules, puis examples, assets, icons, docs. La partie racine est nommée d'après le
composant ; les autres parties sont nommées par l'anatomie de l'archétype (label, icon, control, ...) ou
par vous, imbriquées avec des chemins pointés.
Du CSS et React à Milda
| Vous écririez | Milda dit |
|---|---|
<div>, <span>, <button> | part X: container / text / la racine de l'archétype |
background | fill |
color | ink |
padding, padding-inline-start | inset, inset.inline-start |
margin | rien : gap sur le parent, alignement, ou bleed |
border, border-radius | border <couleur> <épaisseur>, corner |
box-shadow, outline (focus) | elevation, ring |
font-size + line-height, font-weight | text <typographie ou jeton de taille de police>, weight |
display: flex; flex-direction: row | layout { stack row } |
justify-content, align-items, flex: 1 | distribute, align, size fill |
position: absolute | layout { stack layers } + position overlay sur l'enfant |
:hover, :focus-visible, :disabled | state hovered { }, state focus-visible { }, ... |
.btn--secondary, [data-variant] | when variant = Secondary { } |
.card:hover .icon | when ancestor Card hovered { } sur la partie icône |
@media (prefers-color-scheme: dark) | context-group ColorScheme + alias by ColorScheme |
--space-sm: 8px / var(--space-sm) | token space/sm : spacing = 8px / space.sm |
#7c3aed, oklch(...) | hex(7c3aed), oklch(...), uniquement à l'intérieur d'un jeton |
color-mix(), calc() | compute <jeton> in oklch { darken 10% } |
prop onClick | events { click } + emits { click on activate } |
{children} | slot of content default sur la partie qui les contient |
{cond && <X/>} | present when <prop> sur la partie |
z-index, text-align, box-sizing | rien : ordre des parties, le conteneur, il y a une boîte |
Facettes, états et conditions sur une partie
part Panel: container {
layout { stack column; gap space.sm; size fill fit }
fill surface
ink text
border line hairline
corner radius.sm
inset space.sm space.md
when tone = danger {
border danger hairline
}
state hovered {
fill surface-raised
}
state hovered when tone = danger {
fill danger-subtle
}
}
La valeur d'une facette se résout d'abord comme un jeton ; un littéral nu n'est accepté que là où aucun jeton ne revendique
le mot (opacity 50, inset 0). Préférez les jetons. when se base sur les props, state sur les états de
comportement, et les deux se combinent.
Props, événements et contraintes
archetype TextField
props {
label : string required "Ce que le champ demande. Aussi son nom accessible."
value : string
invalid : boolean = false
error : string required when invalid "Affiché sous le contrôle lorsqu'il est invalide."
size : enum(sm, md) = md
tone : context Tone = Neutral
}
events {
valueChange
}
Types : string, number, boolean, enum(a, b), context <Group>, array<T>, vos propres types.
Une chaîne de caractères en fin de ligne est la documentation de la prop. one-of a, b / any-of / exclusive contraignent
des groupes de props.
Où regarder ensuite
- reference/syntax.md : chaque instruction avec un exemple vérifié (jetons et valeurs, disposition, slots, instances, comportements, règles, documentation, assets, icônes).
- reference/vocabulary.md : généré à partir du package de langage. Chaque type de jeton, kind de partie, facette, état, mot de disposition et fonction littérale que le lecteur accepte. En cas de doute sur un mot, regardez ici plutôt que de deviner.
- reference/archetypes.md : généré à partir du prélude. Chaque archétype avec son contrat, son anatomie (quelles parties sont requises) et ses comportements.
- examples/acme.milda : un système complet et propre pour copier des motifs.
- La spécification normative : https://github.com/mildastudio/milda-lang/tree/main/spec (
FORMAT.mdpour ce qu'un fichier promet,GRAMMAR.ebnfpour la syntaxe,PRELUDE.mdpour les archétypes). - Pour un système de design déjà publié, le serveur MCP Milda répond avec ses composants et jetons exacts ; voir https://milda.dev/mcp.
Avant de terminer
- L'en-tête dit
milda 0.10etprelude 0.0.2. - Aucune couleur, longueur ou police littérale dans un composant ; chaque valeur est une référence de jeton.
- Aucun mot de plateforme nulle part : pas de balises, propriétés CSS, pseudo-classes ou noms d'événements de framework.
- Chaque composant commence par un archétype sauf s'il s'agit d'une pure composition d'instances.
milda-check --strictsur l'ensemble du système affiche0 errors, 0 warnings.