@uncinq/design-tokens 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -441
- package/dist/tokens.json +4629 -0
- package/docs/_index.md +119 -0
- package/docs/colors.md +218 -0
- package/docs/customizing.md +77 -0
- package/docs/dark-mode.md +96 -0
- package/docs/dtcg.md +231 -0
- package/docs/naming.md +127 -0
- package/docs/reference.md +128 -0
- package/docs/style-dictionary.md +181 -0
- package/docs/utopia.md +54 -0
- package/package.json +3 -1
package/docs/dtcg.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: DTCG format
|
|
4
|
+
description: The Design Tokens Community Group JSON format, its keys, types and naming conventions as used in this package.
|
|
5
|
+
weight: 6
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
The [W3C Design Token Community Group (DTCG)](https://www.w3.org/community/design-tokens/) defines a standard interchange format for design tokens, so they can travel between tools (Figma, code, documentation) without loss of meaning.
|
|
10
|
+
|
|
11
|
+
`@uncinq/design-tokens` uses DTCG JSON as its source format. [Style Dictionary v5](https://styledictionary.com/) transforms those JSON files into CSS custom properties — see [STYLE-DICTIONARY.md](STYLE-DICTIONARY.md) for the build pipeline. The DTCG spec informs the architecture (primitive → semantic → component, naming conventions, token types).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The DTCG format
|
|
16
|
+
|
|
17
|
+
The [DTCG spec](https://tr.designtokens.org/format/) defines tokens as JSON objects with reserved `$`-prefixed keys:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"color": {
|
|
22
|
+
"brand": {
|
|
23
|
+
"$value": "oklch(0.530 0.195 22.0)",
|
|
24
|
+
"$type": "color",
|
|
25
|
+
"$description": "Primary brand color — used for CTAs and highlights."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Note on OKLCH:** The DTCG `color` type accepts any valid CSS color value, including `oklch(…)`. `@uncinq/design-tokens` uses OKLCH throughout — perceptually uniform, wide-gamut, and natively supported in modern browsers.
|
|
32
|
+
|
|
33
|
+
| Key | Required | Description |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `$value` | ✅ | The token's value |
|
|
36
|
+
| `$type` | recommended | The token type (see below) |
|
|
37
|
+
| `$description` | optional | Human-readable documentation |
|
|
38
|
+
| `$extensions` | optional | Vendor-specific metadata (e.g. Figma) |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## DTCG token types
|
|
43
|
+
|
|
44
|
+
### Scalar types
|
|
45
|
+
|
|
46
|
+
| Type | Example value | CSS usage |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `color` | `oklch(0.530 0.195 22.0)` | `color`, `background-color` |
|
|
49
|
+
| `dimension` | `1rem`, `4px` | `width`, `padding`, `font-size` |
|
|
50
|
+
| `fontFamily` | `"system-ui, sans-serif"` | `font-family` |
|
|
51
|
+
| `fontWeight` | `700` | `font-weight` |
|
|
52
|
+
| `duration` | `300ms` | `transition-duration` |
|
|
53
|
+
| `cubicBezier` | `[0.165, 0.84, 0.44, 1]` | `animation-timing-function` |
|
|
54
|
+
| `number` | `1.5` | `line-height`, `opacity` |
|
|
55
|
+
| `string` | `"uppercase"` | free-form text values |
|
|
56
|
+
| `strokeStyle` | `"solid"`, `"dashed"` | `border-style` |
|
|
57
|
+
|
|
58
|
+
### Composite types
|
|
59
|
+
|
|
60
|
+
| Type | Shape | CSS usage |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `shadow` | `{offsetX, offsetY, blur, spread, color}` | `box-shadow` |
|
|
63
|
+
| `border` | `{width, style, color}` | `border` shorthand |
|
|
64
|
+
| `transition` | `{duration, delay, timingFunction}` | `transition` shorthand |
|
|
65
|
+
| `typography` | `{fontFamily, fontSize, fontWeight, letterSpacing, lineHeight}` | typography rules |
|
|
66
|
+
| `gradient` | `{gradientType, stops[]}` | `background: linear-gradient(…)` |
|
|
67
|
+
|
|
68
|
+
→ Full type list: [tr.designtokens.org/format/#types](https://tr.designtokens.org/format/#types)
|
|
69
|
+
|
|
70
|
+
> **Note on `clamp()` values:** DTCG has no native `fluid` type. Fluid tokens (`--font-size-fluid-sm`, `--font-size-fluid-md`, `--spacing-fluid-*`) use `$type: "dimension"` as the closest match — a documented gap in the spec.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Naming conventions
|
|
75
|
+
|
|
76
|
+
### CSS compound properties → camelCase
|
|
77
|
+
|
|
78
|
+
CSS property names that are two words (kebab-case in CSS) are written as a single **camelCase key** — not as a nested group. The `pathToKebab` transform converts them back to kebab-case for the CSS output, so the result is identical either way.
|
|
79
|
+
|
|
80
|
+
| JSON key | CSS custom property |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `"fontFamily"` | `--btn-font-family` |
|
|
83
|
+
| `"fontSize"` | `--btn-font-size` |
|
|
84
|
+
| `"fontStyle"` | `--btn-font-style` |
|
|
85
|
+
| `"fontWeight"` | `--btn-font-weight` |
|
|
86
|
+
| `"lineHeight"` | `--btn-line-height` |
|
|
87
|
+
| `"maxHeight"` | `--btn-max-height` |
|
|
88
|
+
| `"maxWidth"` | `--btn-max-width` |
|
|
89
|
+
| `"textAlign"` | `--btn-text-align` |
|
|
90
|
+
| `"textDecoration"` | `--btn-text-decoration` |
|
|
91
|
+
| `"textTransform"` | `--btn-text-transform` |
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
// ✅ correct
|
|
95
|
+
"btn": {
|
|
96
|
+
"fontSize": { "$value": "{fontSize.sm}", "$type": "dimension" },
|
|
97
|
+
"fontWeight": { "$value": "{fontWeight.bold}", "$type": "fontWeight" }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ❌ wrong
|
|
101
|
+
"btn": {
|
|
102
|
+
"font": {
|
|
103
|
+
"size": { "$value": "{fontSize.sm}", "$type": "dimension" },
|
|
104
|
+
"weight": { "$value": "{fontWeight.bold}", "$type": "fontWeight" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Exception — semantic namespaces:** `border`, `color`, `padding`, `margin`, `shadow` used to group multiple sub-properties stay nested, because the group key itself is not a CSS property compound word.
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
// ✅ border as a namespace grouping multiple properties
|
|
113
|
+
"border": {
|
|
114
|
+
"radius": { "$value": "{radius.control}", "$type": "dimension" },
|
|
115
|
+
"width": { "$value": "{border.width.sm}", "$type": "dimension" }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ✅ color as a semantic grouping
|
|
119
|
+
"color": {
|
|
120
|
+
"background": { "$value": "{color.background.default}", "$type": "color" },
|
|
121
|
+
"text": { "$value": "{color.text.default}", "$type": "color" }
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Logical properties → sub-keys of `padding` / `margin`
|
|
126
|
+
|
|
127
|
+
Sub-axes of `padding` and `margin` use **CSS logical property names** as sub-keys — not physical directions (`x`, `y`, `top`, `bottom`, `left`, `right`).
|
|
128
|
+
|
|
129
|
+
| Sub-key | CSS logical property | Physical equivalent |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| `"inline"` | `padding-inline` / `margin-inline` | left + right |
|
|
132
|
+
| `"block"` | `padding-block` / `margin-block` | top + bottom |
|
|
133
|
+
| `"inlineStart"` | `padding-inline-start` / `margin-inline-start` | left (LTR) |
|
|
134
|
+
| `"inlineEnd"` | `padding-inline-end` / `margin-inline-end` | right (LTR) |
|
|
135
|
+
| `"blockStart"` | `padding-block-start` / `margin-block-start` | top |
|
|
136
|
+
| `"blockEnd"` | `padding-block-end` / `margin-block-end` | bottom |
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
// ✅ correct
|
|
140
|
+
"padding": {
|
|
141
|
+
"inline": { "$value": "{spacing.sm}", "$type": "dimension" },
|
|
142
|
+
"block": { "$value": "{spacing.xs}", "$type": "dimension" }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ❌ wrong
|
|
146
|
+
"padding": {
|
|
147
|
+
"x": { "$value": "{spacing.sm}", "$type": "dimension" },
|
|
148
|
+
"y": { "$value": "{spacing.xs}", "$type": "dimension" }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Exception — CSS `top`/`bottom`/`left`/`right` as positioning values** (not margin/padding sub-keys) keep their physical names, since they map to CSS position properties, not logical shorthands.
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
// ✅ positioning — physical names are correct here
|
|
156
|
+
"sticky": {
|
|
157
|
+
"top": { "$value": "4rem", "$type": "dimension" }
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### States → nested sub-keys
|
|
162
|
+
|
|
163
|
+
Interactive states (`default`, `hover`, `active`, `disabled`…) are expressed as **nested keys** under the property they modify. The `default` key is automatically stripped by the build transform.
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
"color": {
|
|
167
|
+
"background": {
|
|
168
|
+
"default": { "$value": "{color.brand.default}", "$type": "color" },
|
|
169
|
+
"hover": { "$value": "{color.brand.hover}", "$type": "color" }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
/* output */
|
|
176
|
+
--btn-color-background: var(--color-brand);
|
|
177
|
+
--btn-color-background-hover: var(--color-brand-hover);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
States and camelCase properties compose naturally:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
"color": {
|
|
184
|
+
"textDecoration": {
|
|
185
|
+
"default": { "$value": "transparent", "$type": "color" },
|
|
186
|
+
"hover": { "$value": "{color.link.default}", "$type": "color" }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
--btn-color-text-decoration: transparent;
|
|
193
|
+
--btn-color-text-decoration-hover: var(--color-link);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## References (aliases)
|
|
199
|
+
|
|
200
|
+
Tokens can reference other tokens using `{dotted.path}` syntax:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"color": {
|
|
205
|
+
"link": {
|
|
206
|
+
"default": {
|
|
207
|
+
"$value": "{color.brand.default}",
|
|
208
|
+
"$type": "color"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
In CSS, this maps to `var()`:
|
|
216
|
+
|
|
217
|
+
```css
|
|
218
|
+
--color-link: var(--color-brand);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This is the key mechanism behind the **primitive → semantic → component** hierarchy.
|
|
222
|
+
|
|
223
|
+
→ [tr.designtokens.org/format/#alias](https://tr.designtokens.org/format/#alias)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## References
|
|
228
|
+
|
|
229
|
+
- [DTCG specification](https://tr.designtokens.org/format/) — W3C Community Group draft
|
|
230
|
+
- [DTCG GitHub](https://github.com/design-tokens/community-group) — issues, discussion
|
|
231
|
+
- [Style Dictionary v5](https://styledictionary.com/) — token build pipeline, see [STYLE-DICTIONARY.md](STYLE-DICTIONARY.md)
|
package/docs/naming.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Naming
|
|
4
|
+
description: The naming grammar for semantic and component tokens, the rules that keep it consistent, and the scales in use.
|
|
5
|
+
weight: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
A token name is an API. Once a project references `--color-text-muted`, renaming it is a breaking change, so the grammar below is worth following closely.
|
|
9
|
+
|
|
10
|
+
## Semantic tokens
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
--{category}-{subcategory?}-{variant?}-{state?}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Pattern | Example |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `--{category}` | `--color` |
|
|
19
|
+
| `--{category}-{subcategory}` | `--color-text` |
|
|
20
|
+
| `--{category}-{subcategory}-{variant}` | `--color-text-muted` |
|
|
21
|
+
| `--{category}-{subcategory}-{state}` | `--color-text-disabled` |
|
|
22
|
+
|
|
23
|
+
## Component tokens
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
--{component}-{property}-{sub-property?}-{state?}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The property mirrors the CSS property name, so the token reads the same way as the declaration it controls. Colors are the exception: `color` leads and the role follows, for the reason given in the rules below.
|
|
30
|
+
|
|
31
|
+
| Pattern | Example |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `--{component}` | `--btn` |
|
|
34
|
+
| `--{component}-{property}` | `--btn-padding-inline` |
|
|
35
|
+
| `--{component}-{property}-{sub-property}` | `--btn-color-text-decoration` |
|
|
36
|
+
| `--{component}-{property}-{state}` | `--btn-color-background-hover` |
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
**Lowercase kebab-case**, always.
|
|
41
|
+
|
|
42
|
+
**No component names in primitive or semantic tokens.** `--button-*` belongs in [@uncinq/component-tokens](https://github.com/uncinq/component-tokens), not here. A semantic token that names a component has stopped being semantic.
|
|
43
|
+
|
|
44
|
+
**Semantic tokens are named by intent.** They usually reference a primitive through `var()`, but they may carry a raw value when the value itself is the design decision, such as `--z-index-modal: 400` or `--radius-pill: 9999px`.
|
|
45
|
+
|
|
46
|
+
**`color-[role]` for every color token.** `color` is the category prefix and the UI role follows: `color-background`, `color-border`, `color-text`, `color-accent`, `color-placeholder`. This groups all color tokens alphabetically under `color-*`, and it makes the component token mirror the global one, `--color-background` becoming `--btn-color-background`. `background` is never abbreviated, so `color-background` and never `color-bg`.
|
|
47
|
+
|
|
48
|
+
| Token | Role | CSS property it drives |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `--btn-color-background` | background | `background-color` |
|
|
51
|
+
| `--btn-color-border` | border | `border-color` |
|
|
52
|
+
| `--btn-color-text` | text | `color` |
|
|
53
|
+
| `--btn-color-text-decoration` | text-decoration | `text-decoration-color` |
|
|
54
|
+
| `--form-color-accent` | accent | `color` |
|
|
55
|
+
| `--input-color-placeholder` | placeholder | `color` |
|
|
56
|
+
|
|
57
|
+
**States go last**: `-hover`, `-focus`, `-active`, `-disabled`, `-checked`.
|
|
58
|
+
|
|
59
|
+
**Alphabetical order within a file**, grouped with a comment once a file has many entries.
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
/* Brand */
|
|
63
|
+
--color-brand: var(--color-sienna-600);
|
|
64
|
+
--color-brand-hover: var(--color-sienna-700);
|
|
65
|
+
|
|
66
|
+
/* Text */
|
|
67
|
+
--color-text: var(--color-gray-900);
|
|
68
|
+
--color-text-muted: var(--color-gray-500);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## The `default` convention
|
|
72
|
+
|
|
73
|
+
In the JSON source, a state lives in a nested key and `default` is the unstated one. The build strips it from the generated name, so `color.brand.default` becomes `--color-brand` while `color.brand.hover` becomes `--color-brand-hover`.
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
"brand": {
|
|
77
|
+
"default": { "$value": "{color.sienna.600}", "$type": "color" },
|
|
78
|
+
"hover": { "$value": "{color.sienna.700}", "$type": "color" }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This is what lets `--color-brand` and `--color-brand-hover` sit in the same group without one of them being named `--color-brand-default`. See [DTCG format](../dtcg/) for the full set of authoring conventions.
|
|
83
|
+
|
|
84
|
+
## Scales
|
|
85
|
+
|
|
86
|
+
| Use case | Scale | Example |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| Color palettes | Numeric, 50 to 950 | `--color-gray-500` |
|
|
89
|
+
| Heading levels | Zero-padded, 01 to 06 | `--font-size-heading-01` |
|
|
90
|
+
| Layout and spacing | T-shirt, `2xs xs sm md lg xl 2xl` | `--spacing-md` |
|
|
91
|
+
| Radius, shadow, size | T-shirt | `--radius-sm` |
|
|
92
|
+
| Purposeful aliases | Named | `--radius-control`, `--radius-pill` |
|
|
93
|
+
|
|
94
|
+
The zero-padding on heading levels is there so that `01` through `06` sort correctly as text, which matters because the generated CSS is ordered alphabetically.
|
|
95
|
+
|
|
96
|
+
## Category reference
|
|
97
|
+
|
|
98
|
+
| Category | Covers | Example |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `color` | All color values | `--color-brand`, `--color-text-on-dark` |
|
|
101
|
+
| `font-family` | Typefaces | `--font-family-heading` |
|
|
102
|
+
| `font-size` | Text sizes | `--font-size-sm`, `--font-size-heading-01` |
|
|
103
|
+
| `font-size-fluid` | Responsive fluid type scale | `--font-size-fluid-xl` |
|
|
104
|
+
| `font-weight` | Weight values | `--font-weight-bold` |
|
|
105
|
+
| `line-height` | Line heights | `--line-height-heading` |
|
|
106
|
+
| `letter-spacing` | Tracking | `--letter-spacing-md` |
|
|
107
|
+
| `text-decoration` | Decoration properties | `--text-decoration-offset` |
|
|
108
|
+
| `spacing` | Margin and padding | `--spacing-md`, `--spacing-section` |
|
|
109
|
+
| `spacing-fluid` | Responsive fluid spacing scale | `--spacing-fluid-lg` |
|
|
110
|
+
| `size` | Width and height | `--size-16`, `--size-tablet` |
|
|
111
|
+
| `max-width` | Readability caps | `--max-width-paragraph` |
|
|
112
|
+
| `radius` | Border radius | `--radius-md`, `--radius-pill` |
|
|
113
|
+
| `border` | Border style and width | `--border-width-sm` |
|
|
114
|
+
| `shadow` | Box shadows | `--shadow-md` |
|
|
115
|
+
| `blur` | Blur values | `--blur-md` |
|
|
116
|
+
| `gradient` | Overlay gradients | `--gradient-darken-color-from` |
|
|
117
|
+
| `opacity` | Opacity values | `--opacity-disabled` |
|
|
118
|
+
| `duration` | Animation timing | `--duration-fast` |
|
|
119
|
+
| `easing` | Timing functions | `--easing-out-expo` |
|
|
120
|
+
| `transition` | Shorthand transitions | `--transition-normal` |
|
|
121
|
+
| `ratio` | Aspect ratios | `--ratio-video` |
|
|
122
|
+
| `focus` | Focus ring tokens | `--focus-outline-width` |
|
|
123
|
+
| `grid` | Column counts, gaps, fractions | `--columns-tablet`, `--gap` |
|
|
124
|
+
| `icon` | SVG icons as data URIs | `--icon-arrow` |
|
|
125
|
+
| `z-index` | Stacking order | `--z-index-modal` |
|
|
126
|
+
|
|
127
|
+
For the actual values behind each of these, see the [Reference](../reference/), which is generated from the JSON sources and cannot drift.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Reference
|
|
4
|
+
description: Every token in the package, generated from the JSON sources so it can never drift from the shipped CSS.
|
|
5
|
+
weight: 5
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Every table below is generated at build time from `dist/tokens.json`, which Style Dictionary produces from the same JSON sources as the CSS. Nothing here is written by hand, so a token cannot appear in the reference without existing in the package, or change value without the page changing with it.
|
|
9
|
+
|
|
10
|
+
Values are shown as they are emitted. A semantic token that references a primitive shows `var(--the-primitive)` rather than a flattened value, because that is literally what ships, and it is what makes an override of the primitive propagate.
|
|
11
|
+
|
|
12
|
+
{{< alert-block state="info" >}}
|
|
13
|
+
Reading this offline, from the repository or from `node_modules`? The tables below are rendered by the documentation site. The same data, in machine-readable form, sits in `dist/tokens.json`, and the final CSS is in `dist/css/`.
|
|
14
|
+
{{< /alert-block >}}
|
|
15
|
+
|
|
16
|
+
## Primitive
|
|
17
|
+
|
|
18
|
+
Raw, context-free values. These answer "what is the value?" and carry no opinion about usage.
|
|
19
|
+
|
|
20
|
+
### Color palette
|
|
21
|
+
|
|
22
|
+
19 hues at 11 steps, plus black and white. See [Colors](../colors/) for the hue table and the step guide.
|
|
23
|
+
|
|
24
|
+
{{< tokens pkg="design" file="primitive/color" >}}
|
|
25
|
+
|
|
26
|
+
### Size scale
|
|
27
|
+
|
|
28
|
+
{{< tokens pkg="design" file="primitive/size" >}}
|
|
29
|
+
|
|
30
|
+
### Font primitives
|
|
31
|
+
|
|
32
|
+
{{< tokens pkg="design" file="primitive/font" >}}
|
|
33
|
+
|
|
34
|
+
### Shadow scale
|
|
35
|
+
|
|
36
|
+
{{< tokens pkg="design" file="primitive/shadow" >}}
|
|
37
|
+
|
|
38
|
+
### Blur scale
|
|
39
|
+
|
|
40
|
+
{{< tokens pkg="design" file="primitive/blur" >}}
|
|
41
|
+
|
|
42
|
+
## Semantic
|
|
43
|
+
|
|
44
|
+
Named by purpose. These answer "what is this value for?" and are the layer a project should override.
|
|
45
|
+
|
|
46
|
+
### Color roles
|
|
47
|
+
|
|
48
|
+
{{< tokens pkg="design" file="semantic/color" >}}
|
|
49
|
+
|
|
50
|
+
### Typography
|
|
51
|
+
|
|
52
|
+
Font sizes (fixed and fluid), heading sizes, line heights, letter spacing, and the `--max-width-*` readability caps, which are authored as a `maxWidth` group in this file.
|
|
53
|
+
|
|
54
|
+
{{< tokens pkg="design" file="semantic/typography" >}}
|
|
55
|
+
|
|
56
|
+
### Spacing
|
|
57
|
+
|
|
58
|
+
{{< tokens pkg="design" file="semantic/spacing" >}}
|
|
59
|
+
|
|
60
|
+
### Size aliases
|
|
61
|
+
|
|
62
|
+
{{< tokens pkg="design" file="semantic/size" >}}
|
|
63
|
+
|
|
64
|
+
### Grid
|
|
65
|
+
|
|
66
|
+
{{< tokens pkg="design" file="semantic/grid" >}}
|
|
67
|
+
|
|
68
|
+
### Radius
|
|
69
|
+
|
|
70
|
+
{{< tokens pkg="design" file="semantic/radius" >}}
|
|
71
|
+
|
|
72
|
+
### Border
|
|
73
|
+
|
|
74
|
+
{{< tokens pkg="design" file="semantic/border" >}}
|
|
75
|
+
|
|
76
|
+
### Shadow aliases
|
|
77
|
+
|
|
78
|
+
{{< tokens pkg="design" file="semantic/shadow" >}}
|
|
79
|
+
|
|
80
|
+
### Blur aliases
|
|
81
|
+
|
|
82
|
+
{{< tokens pkg="design" file="semantic/blur" >}}
|
|
83
|
+
|
|
84
|
+
### Gradient
|
|
85
|
+
|
|
86
|
+
{{< tokens pkg="design" file="semantic/gradient" >}}
|
|
87
|
+
|
|
88
|
+
### Opacity
|
|
89
|
+
|
|
90
|
+
{{< tokens pkg="design" file="semantic/opacity" >}}
|
|
91
|
+
|
|
92
|
+
### Motion
|
|
93
|
+
|
|
94
|
+
Durations, easing curves and the transition shorthands built from them.
|
|
95
|
+
|
|
96
|
+
{{< tokens pkg="design" file="semantic/motion" >}}
|
|
97
|
+
|
|
98
|
+
### Ratio
|
|
99
|
+
|
|
100
|
+
{{< tokens pkg="design" file="semantic/ratio" >}}
|
|
101
|
+
|
|
102
|
+
### Focus
|
|
103
|
+
|
|
104
|
+
{{< tokens pkg="design" file="semantic/focus" >}}
|
|
105
|
+
|
|
106
|
+
### Form
|
|
107
|
+
|
|
108
|
+
The largest semantic file. It covers inputs, labels, checkboxes, radios, switches, selects and textareas, and is what [@uncinq/css-base](../../css-base/base/) reads to style native form controls.
|
|
109
|
+
|
|
110
|
+
{{< tokens pkg="design" file="semantic/form" >}}
|
|
111
|
+
|
|
112
|
+
### Icon
|
|
113
|
+
|
|
114
|
+
SVG icons encoded as `url('data:image/svg+xml;utf8,...')`, usable as a `background-image` or a `mask-image`.
|
|
115
|
+
|
|
116
|
+
{{< tokens pkg="design" file="semantic/icon" >}}
|
|
117
|
+
|
|
118
|
+
### Z-index
|
|
119
|
+
|
|
120
|
+
{{< tokens pkg="design" file="semantic/z-index" >}}
|
|
121
|
+
|
|
122
|
+
## Themes
|
|
123
|
+
|
|
124
|
+
### Dark
|
|
125
|
+
|
|
126
|
+
The overlay applied under `prefers-color-scheme: dark`. See [Dark mode](../dark-mode/) for how the selector works and how to opt out.
|
|
127
|
+
|
|
128
|
+
{{< tokens pkg="design" file="themes/dark" >}}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Style Dictionary
|
|
4
|
+
description: How the JSON token sources are compiled into CSS custom properties, including the dark theme pass.
|
|
5
|
+
weight: 7
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
[Style Dictionary v5](https://styledictionary.com/) transforms the DTCG JSON token files into CSS custom properties.
|
|
10
|
+
|
|
11
|
+
## Run the build
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Output is written to `dist/css/`. One CSS file is generated per JSON source file, preserving the same directory structure:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
tokens/primitive/color.json → dist/css/primitive/color.css
|
|
21
|
+
tokens/semantic/color.json → dist/css/semantic/color.css
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Config — `style-dictionary.config.js`
|
|
27
|
+
|
|
28
|
+
The config is a single ES module. It registers a custom name transform and a custom format, then maps every JSON file to a CSS output.
|
|
29
|
+
|
|
30
|
+
### Name transform — `name/kebab/strip-default`
|
|
31
|
+
|
|
32
|
+
Converts the token path to a kebab-case CSS custom property name, dropping any `default` segment:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
color.background.default → --color-background
|
|
36
|
+
color.text.muted → --color-text-muted
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Format — `css/layer-tokens`
|
|
40
|
+
|
|
41
|
+
All tokens are wrapped in `@layer tokens { :root { … } }`. This is a low-priority layer in the Un Cinq stack (order: `reset, tokens, base, layouts, vendors, components`), so any project can override any token by importing after this package inside its own `@layer tokens` block.
|
|
42
|
+
|
|
43
|
+
References are preserved as `var()` — tokens are **not** resolved to their final values:
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
/* output */
|
|
47
|
+
--color-text: var(--color-gray-900); /* not oklch(0.208 0.006 264.542) */
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This is done by reading `token.original.$value` and replacing every `{path.to.token}` reference with `var(--path-to-token)`. References embedded in a string value are also handled:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "$value": "{size.8} {size.12}" }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
--file-button-padding: var(--size-8) var(--size-12);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Composite tokens
|
|
61
|
+
|
|
62
|
+
Tokens whose `$value` is an object or an array of objects (e.g. `shadow`) are serialized as a space-separated CSS value. References inside the composite are converted to `var()`.
|
|
63
|
+
|
|
64
|
+
**Property order matters** — list properties in the order CSS expects them. For `box-shadow`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"shadow": {
|
|
69
|
+
"sm": {
|
|
70
|
+
"$type": "shadow",
|
|
71
|
+
"$value": [
|
|
72
|
+
{
|
|
73
|
+
"offsetX": "0",
|
|
74
|
+
"offsetY": "1px",
|
|
75
|
+
"blur": "2px",
|
|
76
|
+
"spread": "0",
|
|
77
|
+
"color": "{color.shadow.light}",
|
|
78
|
+
"inset": false
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
--shadow-sm: 0 1px 2px 0 var(--color-shadow-light);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`inset` is always treated as a boolean prefix (`inset` or nothing), not a positional value.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Dark mode — `themes/dark.json`
|
|
95
|
+
|
|
96
|
+
Dark mode is a **separate token set**, `tokens/themes/dark.json` — **not** declared inline on tokens (there is no `$mods`/`$modes` extension).
|
|
97
|
+
|
|
98
|
+
The model is **default + override**:
|
|
99
|
+
|
|
100
|
+
- The `semantic` layer carries the **default (light)** values, at their natural place (`semantic/color.json`, `semantic/form.json`). There is **no** `themes/light.json` — light is the baseline, not a mode you switch into.
|
|
101
|
+
- `themes/dark.json` holds **only the tokens that differ** in dark mode (background, text, heading, shadow, border, form background). Everything mode-invariant (brand, accent, status, …) is inherited automatically through `var()`.
|
|
102
|
+
|
|
103
|
+
This mirrors the DTCG **Resolver** philosophy (a base set + an overlay holding only the differences), so it stays forward-compatible as the Resolver spec stabilises.
|
|
104
|
+
|
|
105
|
+
### Generated output
|
|
106
|
+
|
|
107
|
+
The build emits `dist/css/themes/dark.css` with a single rule, inside `@layer tokens`:
|
|
108
|
+
|
|
109
|
+
```css
|
|
110
|
+
@layer tokens {
|
|
111
|
+
@media (prefers-color-scheme: dark) {
|
|
112
|
+
:root:not([data-color-scheme="light"]) {
|
|
113
|
+
--color-background: var(--color-gray-950);
|
|
114
|
+
/* … */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The light defaults stay in `dist/css/semantic/*.css` on `:root`. The dark file is purely additive — a light-only (mono-mode) site never needs to load it.
|
|
121
|
+
|
|
122
|
+
### Activation matrix
|
|
123
|
+
|
|
124
|
+
Dark mode follows the OS preference. The only attribute that does anything is `data-color-scheme="light"`, which **forces light** even when the OS prefers dark. Forcing dark against a light OS is intentionally **not** supported (there is no `[data-color-scheme="dark"]` rule).
|
|
125
|
+
|
|
126
|
+
| `data-color-scheme` on `<html>` | OS preference | Result | Why |
|
|
127
|
+
| --- | --- | --- | --- |
|
|
128
|
+
| *(none)* | light | **light** | `:root` defaults; the media query is inactive |
|
|
129
|
+
| *(none)* | dark | **dark** | the media rule matches `:root` (not forced light) |
|
|
130
|
+
| `light` | dark | **light** | `:not([data-color-scheme="light"])` excludes it → `:root` defaults |
|
|
131
|
+
| `light` | light | **light** | `:root` defaults; the media query is inactive |
|
|
132
|
+
| *(any other)* | dark | **dark** | the media rule still matches — only `light` opts out |
|
|
133
|
+
|
|
134
|
+
> **Note** — `color-scheme: dark` is intentionally absent from the generated block. The `color-scheme` property is managed at the HTML level by the consuming application (e.g. via `<meta name="color-scheme">`), not by the token layer.
|
|
135
|
+
|
|
136
|
+
### Rules
|
|
137
|
+
|
|
138
|
+
- Only colour tokens that actually change between modes belong in `themes/dark.json`; everything mode-invariant stays in `semantic`.
|
|
139
|
+
- Dark values follow the same `{dotted.path}` reference syntax as `$value` (e.g. `{color.gray.950}`).
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Adding a new token file
|
|
144
|
+
|
|
145
|
+
1. Create a JSON file anywhere under `tokens/` with DTCG structure.
|
|
146
|
+
2. Run `npm run build` — the file is detected automatically.
|
|
147
|
+
3. A matching CSS file is generated in `dist/css/`.
|
|
148
|
+
|
|
149
|
+
No changes to `style-dictionary.config.js` are needed.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## References
|
|
154
|
+
|
|
155
|
+
- [Style Dictionary v5 docs](https://styledictionary.com/)
|
|
156
|
+
- [DTCG format](DTCG.md) — token structure and types
|
|
157
|
+
- [Utopia fluid scales](UTOPIA.md) — `clamp()` values in spacing and typography tokens
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## The JSON manifest
|
|
162
|
+
|
|
163
|
+
Alongside the CSS, the build writes `dist/tokens.json`: a flat array of every token the package ships.
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
"name": "--color-brand",
|
|
169
|
+
"value": "var(--color-sienna-600)",
|
|
170
|
+
"type": "color",
|
|
171
|
+
"file": "semantic/color",
|
|
172
|
+
"description": ""
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The documentation site renders its reference tables from this file, which is why the reference cannot drift from the stylesheets.
|
|
178
|
+
|
|
179
|
+
The guarantee comes from a single shared function. `tokenToCssValue()` serializes a token to its CSS value, and **both** the CSS format and the manifest format call it. There is no second implementation of the naming or the value logic to fall out of step, and the build asserts the equivalence: the manifest holds exactly one entry per declaration emitted in `dist/css/`, with the same name and the same value.
|
|
180
|
+
|
|
181
|
+
`description` comes from the DTCG `$description` key. Adding one to a token source makes it appear in the published reference with no other change.
|