Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,20 @@ description: <string> # optional
omitted: <string[] | OmittedSection[]> # optional, list of sections to intentionally omit
colors:
<token-name>: <Color>
<group-name>: # optional nested group
<token-name>: <Color>
typography:
<token-name>: <Typography>
<group-name>: # optional nested group
<token-name>: <Typography>
rounded:
<scale-level>: <Dimension>
<group-name>: # optional nested group
<scale-level>: <Dimension>
spacing:
<scale-level>: <Dimension | number>
<group-name>: # optional nested group
<scale-level>: <Dimension | number>
components:
<component-name>:
<token-name>: <string | token reference>
Expand All @@ -133,7 +141,7 @@ components:
|:-----|:-------|:--------|
| Color | Any CSS color (hex, `rgb()`, `oklch()`, named, etc.) | `"#1A1C1E"`, `"oklch(62% 0.18 250)"` |
| Dimension | number + unit (`px`, `em`, `rem`) | `48px`, `-0.02em` |
| Token Reference | `{path.to.token}` | `{colors.primary}` |
| Token Reference | `{path.to.token}` | `{colors.primary}`, `{colors.light.surface}`, `{typography.sm.headline-lg}` |
| Typography | object with `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, `letterSpacing`, `fontFeature`, `fontVariation` | See example above |

### Section Order
Expand Down Expand Up @@ -177,6 +185,8 @@ Variants (hover, active, pressed) are expressed as separate component entries wi
| Unknown section heading | Preserve; do not error |
| Unknown color token name | Accept if value is valid |
| Unknown typography token name | Accept as valid typography |
| Grouped token sub-map | Flatten to dot-separated token path |
| Flat and grouped token name collision | Error; reject the conflicting token |
| Unknown component property | Accept with warning |
| Duplicate section heading | Error; reject the file |

Expand Down
86 changes: 81 additions & 5 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,20 @@ description: <string> # optional
omitted: <string[]|OmittedSection[]> # optional
colors:
<token-name>: <Color>
<group-name>: # optional nested group
<token-name>: <Color>
typography:
<token-name>: <Typography>
<group-name>: # optional nested group
<token-name>: <Typography>
rounded:
<scale-level>: <Dimension>
<group-name>: # optional nested group
<scale-level>: <Dimension>
spacing:
<scale-level>: <Dimension | number>
<group-name>: # optional nested group
<scale-level>: <Dimension | number>
components:
<component-name>:
<token-name>: <string|token reference>
Expand Down Expand Up @@ -95,7 +103,9 @@ Hex notation (`#RRGGBB`) remains the recommended default for simplicity and broa
reason: "No rounded corners defined in brand book"
```

**Token References**: A token reference must be wrapped in curly braces, and contain an object path to another value in the YAML tree. For most token groups, the reference must point to a primitive value (e.g., `colors.primary-60`), not a group (e.g., `colors`). Within the `components` section, references to composite values (e.g., `{typography.label-md}`) are permitted.
**Grouped Tokens**: Top-level token categories (`colors`, `typography`, `rounded`, and `spacing`) accept a single level of optional nested YAML sub-maps to organize tokens into logical groups. Grouped tokens are flattened internally to dot-separated paths (e.g., `<category>.<group-name>.<token-name>`); nesting beyond one group level is rejected as an error. Mixing flat hyphenated keys (e.g., `colors.primary-light`) and nested grouped keys (e.g., `colors.primary.light`) that produce identical flattened CSS custom property names (`--color-primary-light`) is also rejected as an error by the linter, as are duplicate token paths.

**Token References**: A token reference must be wrapped in curly braces, and contain a dot-separated object path to another value in the YAML tree. For most token groups, the reference must point to a primitive value (e.g., `colors.primary-60` or `colors.light.surface`), not a group (e.g., `colors` or `colors.light`). Within the `components` section, references to composite values (e.g., `{typography.label-md}` or `{typography.sm.headline-lg}`) are permitted.

# Sections

Expand Down Expand Up @@ -150,7 +160,7 @@ The palette is rooted in high-contrast neutrals and a single, evocative accent c
The `colors` section defines all color design tokens. The color tokens should be derived from the key color palettes defined in the markdown prose. The exact mapping from color palettes to color tokens may follow any consistent naming convention.

It is a
map\<string, Color>, that maps the name of the color token to its value.
map\<string, Color> (with optional nested group sub-maps), that maps the name or dot-separated group path of the color token to its value.

```yaml
colors:
Expand All @@ -160,6 +170,27 @@ colors:
neutral: "#F7F5F2"
```

For adaptive light and dark themes, mode-agnostic brand and semantic roles can remain at the top level while mode-dependent surface and text tokens are grouped under optional `light` and `dark` sub-maps:

```yaml
colors:
primary: "#647D66"
on-primary: "#FFFFFF"
secondary: "#FF8A65"
on-secondary: "#FFFFFF"
error: "#BA1A1A"
light:
surface: "#FAFDF7"
on-surface: "#1A1C19"
surface-container: "#FFFFFF"
surface-container-low: "#F4F7F1"
dark:
surface: "#10130E"
on-surface: "#E2E4DE"
surface-container: "#1A1C19"
surface-container-low: "#14170F"
```

## Typography

This section defines typography levels.
Expand Down Expand Up @@ -190,7 +221,7 @@ the narrative and **Space Grotesk** for technical data.
The `typography` section defines the precise font properties for the typography design tokens.

It is a
map\<string, Typography>
map\<string, Typography> (with optional nested group sub-maps).

```yaml
typography:
Expand All @@ -213,6 +244,29 @@ typography:
letterSpacing: 0.1em
```

For responsive typography, tokens can be grouped under breakpoint sub-maps while breakpoint-agnostic styles remain at the top level. The recommended (but not required) group names for responsive design are `sm`, `md`, `lg`, and `xl` (mobile, tablet, laptop, and desktop, respectively):

```yaml
typography:
body-md:
fontFamily: Public Sans
fontSize: 16px
fontWeight: 400
lineHeight: 1.6
sm:
headline-lg:
fontFamily: Public Sans
fontSize: 32px
fontWeight: 600
lineHeight: 1.15
lg:
headline-lg:
fontFamily: Public Sans
fontSize: 48px
fontWeight: 600
lineHeight: 1.1
```

## Layout

Also known as "Layout & Spacing".
Expand All @@ -237,7 +291,7 @@ A strict 8px spacing scale (with a 4px half-step for micro-adjustments) is used
The spacing section defines the spacing design tokens. These may include spacing units that are useful for implementing the layout model. For example, a fixed grid layout may have spacing units for column spans, gutters, and margins.

It is a
map\<string, Dimension | number> that maps the spacing scale identifier to a dimension value or a unitless number (e.g., column counts or ratios).
map\<string, Dimension | number> (with optional nested group sub-maps) that maps the spacing scale identifier or group path to a dimension value or a unitless number (e.g., column counts or ratios).

```yaml
spacing:
Expand All @@ -251,6 +305,22 @@ spacing:
margin: 32px
```

For responsive layout metrics, spacing tokens can also be grouped under breakpoint sub-maps (recommended: `sm`, `md`, `lg`, and `xl` for mobile, tablet, laptop, and desktop, respectively):

```yaml
spacing:
base: 16px
xs: 4px
sm:
gutter: 16px
margin: 16px
columns: 4
lg:
gutter: 24px
margin: 32px
columns: 12
```

## Elevation & Depth

Also known as "Elevation".
Expand Down Expand Up @@ -286,7 +356,7 @@ engineered aesthetic.
The `rounded` section defines the design tokens for rounded corners used in
buttons, cards, and other rectangular shapes.

It is a map\<string, Dimension>.
It is a map\<string, Dimension> (with optional nested group sub-maps).

```yaml
rounded:
Expand Down Expand Up @@ -363,6 +433,10 @@ The following names are commonly used across design systems. They are not requir

**Rounded:** `none`, `sm`, `md`, `lg`, `xl`, `full`

**Theme Mode Groups:** `light`, `dark`

**Responsive Breakpoint Groups:** `sm` (mobile), `md` (tablet), `lg` (laptop), `xl` (desktop)

# Consumer Behavior for Unknown Content

When a DESIGN.md consumer encounters content not defined by this spec:
Expand All @@ -373,5 +447,7 @@ When a DESIGN.md consumer encounters content not defined by this spec:
| Unknown color token name | Accept if value is valid | `surface-container-high: '#ede7dd'` |
| Unknown typography token name | Accept as valid typography | `telemetry-data` |
| Unknown spacing value | Accept; store as string if not a valid dimension | `grid-columns: '5'` |
| Grouped token sub-map | Flatten to dot-separated token path | `colors.light.surface`, `typography.sm.headline-lg` |
| Flat and grouped token name collision | Error; reject the conflicting token | `colors.primary-light` and `colors.primary.light` |
| Unknown component property | Accept with warning | `borderColor` |
| Duplicate section heading | Error; reject the file | Two `## Colors` headings |
79 changes: 79 additions & 0 deletions packages/cli/src/linter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,83 @@ motion:
);
expect(unknownKeyFindings).toEqual([]);
});

it('processes grouped tokens across colors, typography, spacing, and rounded', () => {
const content = `---
name: Adaptive Design System

colors:
primary: "#647D66"
on-primary: "#FFFFFF"
light:
surface: "#FAFDF7"
on-surface: "#1A1C19"
dark:
surface: "#10130E"
on-surface: "#E2E4DE"

typography:
body-md:
fontFamily: Public Sans
fontSize: 16px
fontWeight: 400
lineHeight: 1.6
sm:
headline-lg:
fontFamily: Public Sans
fontSize: 32px
fontWeight: 600
lineHeight: 1.15
lg:
headline-lg:
fontFamily: Public Sans
fontSize: 48px
fontWeight: 600
lineHeight: 1.1

rounded:
sm: 4px
control:
pill: 9999px

spacing:
base: 16px
sm:
gutter: 16px
lg:
gutter: 24px

components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
typography: "{typography.sm.headline-lg}"
rounded: "{rounded.control.pill}"
padding: "{spacing.sm.gutter}"
card-light:
backgroundColor: "{colors.light.surface}"
textColor: "{colors.light.on-surface}"
card-dark:
backgroundColor: "{colors.dark.surface}"
textColor: "{colors.dark.on-surface}"
---

## Overview

Adaptive design system with grouped theme and responsive tokens.
`;

const result = lint(content);

expect(result.summary.errors).toBe(0);
expect(result.designSystem.colors.size).toBe(6);
expect(result.designSystem.typography.size).toBe(3);
expect(result.designSystem.rounded.size).toBe(2);
expect(result.designSystem.spacing.size).toBe(3);
expect(result.designSystem.typography.get('sm.headline-lg')?.fontSize?.value).toBe(32);
expect(result.designSystem.typography.get('lg.headline-lg')?.fontSize?.value).toBe(48);
expect(result.designSystem.spacing.get('sm.gutter')?.value).toBe(16);
expect(result.designSystem.spacing.get('lg.gutter')?.value).toBe(24);
});
});

Loading
Loading