Grammar v2 — the design taste specification
A 30-axis open standard for measuring design taste. Forkable. Citable. Built so any LLM, design tool, or brand team can speak the same language about visual decisions.
§Abstract
Grammar v2 is an open specification that decomposes brand design taste into 30 named axes across surface, palette, typography, emphasis, whitespace, voice, motion, and imagery.
It exists so that arguments about design — usually subjective, frequently circular — can be grounded in a shared vocabulary that machines and humans both speak. Every axis has an enumerated value set, a scoring scale (ordinal or nominal), and a documented extraction method. A brand, a generated webpage, or an LLM‑authored mockup can each be expressed as a vector over these 30 axes and compared point‑by‑point.
The intended audience is threefold: agents and LLMs that need a stable target schema for design output; design tools (linters, generators, brand systems) that want to declare conformance; and brand teams that need a portable, version‑pinned definition of their visual identity. If OpenAPI describes the shape of HTTP, Grammar v2 describes the shape of taste.
§Status & versioning
This document specifies grammar-v2.0.0, released 2026-05-17. The canonical
machine-readable form lives at /schema/grammar-v2.json.
Semver policy
-
MAJOR— an axis is removed, renamed, or its value set changes in a breaking way. Existing vectors require migration. -
MINOR— a new axis is added, or a new allowed value is appended to an existing axis. Existing vectors remain valid. -
PATCH— clarifications to rubrics, descriptions, or example sets. No effect on validation.
Implementations must pin to a specific major.minor in audit blocks.
Computed weights and per-axis convergence priors are versioned separately in
/api/weights.json.
§Conformance levels
A tool, brand, or service may declare conformance at one of four levels. Higher levels include everything from the levels below.
-
L0DeclarativeTool or brand references Grammar v2 by URL. No machine-checkable claim about output — just a stated commitment to the vocabulary.
-
L1ProducerEmits axis vectors that pass JSON Schema validation against
grammar-v2.json. Values are syntactically valid; semantic accuracy not yet verified. -
L2Producer + ValidatedVectors are verified against a hand-graded reference set (the Golden Set). Inter-axis agreement above the published threshold.
-
L3Reference tierHand-graded by maintainers. Anchors the calibration chain — all L2 implementations are evaluated against L3 vectors.
§The 30 axes
Each axis is identified by a dotted key (category.name), an enumerated value set,
and a scale type. Distance scoring for ordinal axes uses
c = clip(1 − |b − o| / (max − 1), 0, 1); nominal axes use binary match until a
proximity matrix is published. Every row below is individually addressable via
#axis-<key>.
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| surface.ground | Page ground | nominal | deterministic | whitecreamgray-lightgray-darkblackwarm-tintedcool-tinted | anthropic · cream |
| surface.radius | Corner radius | ordinal | deterministic | 0px4-6px8-10px12-16pxpill | stripe · 4-6px |
| surface.borders | Border philosophy | ordinal | deterministic | nonehairlinemediumheavy | linear · hairline |
| surface.shadows | Shadow / elevation | ordinal | deterministic | nonesubtlelayereddramatic | stripe · subtle |
| surface.texture | Surface texture | nominal | model_graded | flatgraingradientmeshphoto | stripe · mesh |
| surface.dark_mode | Dark mode handling | nominal | deterministic | nonesystem-adaptiveinversioncustom-palettedark-first | linear · dark-first |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| palette.strategy | Color strategy | ordinal | deterministic | monomono+1duotonetriadicfull-spectrum | stripe · full-spectrum |
| palette.saturation | Accent saturation | ordinal | deterministic | desaturatedmutedbalancedvividneon | antimetal · neon |
| palette.warmth | Color temperature | nominal | deterministic | coolneutralwarmmixed | anthropic · warm |
| palette.accent_use | Accent frequency | ordinal | deterministic | rare-signalrecurringpervasive | stripe · pervasive |
| palette.contrast | Text contrast | ordinal | deterministic | lowmediumhighextreme | acne-studios · extreme |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| type.pairing | Typeface pairing | nominal | mixed | serif/sanssans/sanssans/monoserif/grotesquegrotesque/monosingle-family | anthropic · serif/sans |
| type.heading_weight | Display heading weight | ordinal | deterministic | ultralightlightregularmediumboldblack | anthropic · light |
| type.body_size | Body text size | ordinal | deterministic | smallregularlarge | acne-studios · large |
| type.display_scale | Display-to-body scale | ordinal | deterministic | compactbalancedoversized | acne-studios · oversized |
| type.tracking | Display tracking | ordinal | deterministic | negativetightnormalloose | anthropic · negative |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| emphasis.mechanism | Emphasis device | nominal | model_graded | weightcolorunderlinescalespacing | stripe · color |
| emphasis.cta_treatment | Primary CTA treatment | ordinal | mixed | underline-onlytext-linkoutlinedfilled-solidfilled-gradient | stripe · filled-gradient |
| emphasis.density_signals | Section density signals | nominal | model_graded | dividerswhitespacebordersgrouping-cards | apple · whitespace |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| whitespace.discipline | Whitespace generosity | ordinal | mixed | densemediumgenerousextreme | acne-studios · extreme |
| whitespace.section_gap | Section gap | ordinal | deterministic | compactbalancedgrand | apple · grand |
| whitespace.element_gap | Element gap | ordinal | deterministic | tightmediumairy | acne-studios · airy |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| voice.archetype | Voice archetype | nominal | model_graded | librarianengineerartistfriendconciergecuratorchallengergeneric | linear · engineer |
| voice.formality | Voice formality | ordinal | mixed | casualconversationalprofessionalformal | mercury · professional |
| voice.hedging | Hedging frequency | ordinal | deterministic | nonelowmediumhigh | linear · none |
| voice.sentence_length | Sentence length | ordinal | deterministic | tersebalancedflowing | linear · terse |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| motion.budget | Motion budget | ordinal | deterministic | noneaccentrecurringpervasive | stripe · pervasive |
| motion.character | Motion character | nominal | model_graded | mechanicalsmoothplayfulcinematic | phantom-studios · cinematic |
| motion.trigger | Motion trigger | nominal | model_graded | hover-onlyscrollautonomousgesture | stripe · autonomous |
| Key | Label | Scale | Extraction | Values | Example |
|---|---|---|---|---|---|
| imagery.strategy | Imagery strategy | nominal | model_graded | nonephotographicillustrationabstractmixed | duolingo · illustration |
§JSON shape
Every brand entry under /api/styles/<brand>.json includes a
grammar field — a complete 30-axis vector keyed by category. Below is the
verbatim shape from Stripe’s entry (an L3 reference-tier brand).
"grammar": { "surface": { "ground": "white", "radius": "4-6px", "borders": "hairline", "shadows": "subtle", "texture": "mesh", "dark_mode": "none" }, "palette": { "strategy": "full-spectrum", "saturation": "balanced", "warmth": "mixed", "accent_use": "pervasive", "contrast": "high" }, "type": { "pairing": "sans/sans", "heading_weight": "medium", "body_size": "regular", "display_scale": "balanced", "tracking": "normal" }, "emphasis": { "mechanism": "color", "cta_treatment": "filled-gradient", "density_signals": "grouping-cards" }, "whitespace": { "discipline": "medium", "section_gap": "balanced", "element_gap": "medium" }, "voice": { "archetype": "engineer", "formality": "conversational", "hedging": "low", "sentence_length": "balanced" }, "motion": { "budget": "pervasive", "character": "smooth", "trigger": "autonomous" }, "imagery": { "strategy": "abstract" } }
Live: /api/styles/stripe.json ·
Validation schema: brand-style-v2.json
§“Speak Grammar v2” badge
If your tool, brand site, or LLM output emits valid Grammar v2 vectors, you may display this badge. It is a single inline SVG with no external dependencies.
§Implementations
Reference implementations maintained alongside the spec. All speak Grammar v2 natively.
- /eval The calibration surface. Compares judge outputs to the Golden Set and computes inter-axis agreement.
- /api/score Judge endpoint. Accepts a brief + URL, emits a scored vector with verdict, strongest and weakest axes.
- /scorer Browser UI for the judge endpoint. Paste a brief and a URL, get a scored Grammar v2 vector back.
- cli/ Python and Node CLIs. Local extraction, batch scoring, CI-ready exit codes.
- mcp/ Model Context Protocol server. Lets Claude, ChatGPT, and other agents query the catalog and spec.
- RFC-001 The motivating RFC. Why a grammar exists at all, what it replaces, and how it’s calibrated.