Milda
Milda 是一個附帶圖形化介面的設計系統編譯器。一個 .milda 檔案就是設計系統:
基礎(Token)、元件、使用規則、文件與資產。React 套件、Figma 元件庫、
文件網站以及 AI 可讀的檔案都從它產生。你永遠不需要編輯輸出;你只要修改
檔案再重新產生即可。
此技能針對0.10語言表面與0.0.2前奏曲。你撰寫的每個檔案都必須以這兩行開頭。
循環:撰寫、檢查、修復
在讀取器(reader)同意之前,切勿告訴使用者 .milda 檔案是有效的。每次編輯後,執行:
npx -y -p @mildastudio/milda-lsp milda-check --strict path/to/system/
給它目錄,或一次給系統中的所有檔案,因為一個檔案中宣告的 Token 會解析另一個檔案中的參考。
它會輸出 path:line:col severity [code] message,與編輯器擴充功能顯示的診斷訊息相同,
並且在有任何錯誤時(或搭配 --strict 時有任何警告)以非零狀態碼退出。修復它報告的內容並
再次執行,直到它顯示 0 errors, 0 warnings。警告不是雜訊:unknown facet "background" skipped 表示那一行沒有作用。
要向使用者展示結果,位於 https://play.milda.dev 的 Playground 可以即時貼上檔案並渲染,無需帳號。
以 Milda 思考:十條規則
大多數錯誤來自於用 Milda 的語法撰寫 CSS、DOM 樹或 React 元件。這些規則 正是該語法存在的目的。
- 系統是源頭;程式碼是輸出。 所有與平台相關的決策都在產生時決定, 因此檔案中永遠不會包含 CSS 屬性、標籤、框架的事件名稱或 pseudo-class。
- 說出一件事物是什麼,而非它由什麼組成。 部件有中立的_種類_(
container、text、input、icon、control...),永遠不會是div或button。每個目標平台會選擇自己的元素。 - 值有名稱,名稱有層級。 元件永遠不會使用字面值著色。facet 指向一個 Token;
Semantic層級 為字面值的Base層級設定別名。重新設計風格就是修改基礎。 - 上下文是一個維度,不是一個副本。 深色模式、密度、品牌:各自是一個
context-group,一個 Token 透過alias by Group { ... }針對每個上下文給出答案。由上下文群組型別化的 prop(variant) 在每個實例中使用相同機制。 - 狀態來自行為。
hovered、pressed、focus-visible、disabled、open、selected由原型的行為發出。使用state hovered { }來設定它們的樣式,永遠不要使用任何 與指標或平台相關的詞語。列表是封閉的(reference/vocabulary.md),讀取器會對任何其他詞語發出警告:state hover會被報告並提示它可能想拼寫的是哪個詞。 - 合約就是識別。
props和events是消費者編寫程式碼時所依賴的內容,也是版本差異比較的對象。 根據意義命名(variant、checkedChange),而不是根據平台。 - 從既有的內容建構。 從前奏曲中的一個
archetype開始建立元件:它帶來了合約形狀、 必需的部件和行為(鍵盤、ARIA、焦點)。使用instance部件將較大的元件 從較小的元件組合而成。 - 每個參考都使用名稱。 部件使用點分隔路徑(
Header.Title),Token 使用路徑 (space.sm代表space/sm),元件使用識別符。沒有 id。註解 (#) 是唯一 不會在往返過程中保留的東西。 - 透過接縫擴展,永遠不要修補輸出。 當詞彙無法表達某些內容時,使用
slot、每個目標的選項, 或最終手段是使用標記了目標的foreign內容。永遠不要建議編輯產生的程式碼。 - 說出它應該如何使用。 將介面約束(
required、required when、one-of)放在props中,將組合規則(rule ... { never X within Y })放在頂層, 每個都帶有原因。
一個檔案,從上到下
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 "The primary action." {
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
}
}
順序:header、meta、context-group、group(元件庫章節)、foundations、type、
component、rule,然後是 example、assets、icons、docs。根部件以元件命名;
其他部件由原型的解剖結構命名(label、icon、control...)或由你使用點分隔路徑嵌套命名。
從 CSS 和 React 到 Milda
| 你習慣寫的 | Milda 的說法 |
|---|---|
<div>, <span>, <button> | part X: container / text / 原型的根部件 |
background | fill |
color | ink |
padding, padding-inline-start | inset, inset.inline-start |
margin | 無:使用父層的 gap、對齊或 bleed |
border, border-radius | border <color> <width>, corner |
box-shadow, outline (焦點) | elevation, ring |
font-size + line-height, font-weight | text <typography or font-size token>, 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 |
:hover, :focus-visible, :disabled | state hovered { }, state focus-visible { }, ... |
.btn--secondary, [data-variant] | when variant = Secondary { } |
.card:hover .icon | 在圖示部件上使用 when ancestor Card hovered { } |
@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(...),僅在 Token 內部使用 |
color-mix(), calc() | compute <token> in oklch { darken 10% } |
onClick prop | events { click } + emits { click on activate } |
{children} | 在容納它們的部件上使用 slot of content default |
{cond && <X/>} | 在部件上使用 present when <prop> |
z-index, text-align, box-sizing | 無:部件順序、容器,只有一個盒子 |
部件上的 Facet、狀態與條件
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
}
}
facet 的值首先解析為 Token;只有在沒有 Token 聲明該詞的情況下,才會接受單純的字面值
(opacity 50、inset 0)。優先使用 Token。when 作用於 props,state 作用於行為狀態,
兩者可以組合。
Props、事件與約束
archetype TextField
props {
label : string required "欄位詢問的內容,也是它的可訪問名稱。"
value : string
invalid : boolean = false
error : string required when invalid "在無效時顯示在控制項下方。"
size : enum(sm, md) = md
tone : context Tone = Neutral
}
events {
valueChange
}
型別:string、number、boolean、enum(a, b)、context <Group>、array<T>、你自己定義的 type。
尾隨的字串是 prop 的文件。one-of a, b / any-of / exclusive 約束一組 prop。
下一步
- reference/syntax.md:每條陳述式都附帶經過驗證的範例(Token 和值、佈局、插槽、實例、行為、規則、文件、資產、圖示)。
- reference/vocabulary.md:從語言套件產生。讀取器接受的每種 Token 型別、部件種類、facet、狀態、佈局詞彙和字面值函式。 對某個詞有疑問時,請查閱此處而不是亂猜。
- reference/archetypes.md:從前奏曲產生。每個原型及其合約、解剖結構(哪些部件是必需的)和行為。
- examples/acme.milda:一個完整、乾淨的系統,可以從中複製模式。
- 規範文件:https://github.com/mildastudio/milda-lang/tree/main/spec(`FORMAT.md` 說明檔案保證了什麼,
GRAMMAR.ebnf說明語法,PRELUDE.md說明原型)。 - 對於已發布的設計系統,Milda MCP 伺服器會以精確的元件和 Token 回應;請參閱 https://milda.dev/mcp。
完成之前
- 檔案標頭必須是
milda 0.10和prelude 0.0.2。 - 元件中不能有任何字面顏色、長度或字型;每個值都必須是 Token 參考。
- 任何地方都不能出現平台相關詞語:沒有標籤、CSS 屬性、pseudo-class 或框架事件名稱。
- 除非是純粹由實例組合而成,否則每個元件都必須從一個原型開始。
- 在整個系統上執行
milda-check --strict必須顯示0 errors, 0 warnings。