| 1 | { |
| 2 | "schemaVersion": 2, |
| 3 | "generatedAt": "2026-09-02T22:42:36Z", |
| 4 | "title": "Design System: Fiesta", |
| 5 | "extensions": { |
| 6 | "colorMeta": { |
| 7 | "bg": { |
| 8 | "role": "neutral", |
| 9 | "displayName": "Page", |
| 10 | "canonical": "light-dark(#fbfbfc, #0c0d0f)", |
| 11 | "tonalRamp": [ |
| 12 | "#0b0d10", |
| 13 | "#111418", |
| 14 | "#1a1f26", |
| 15 | "#2a3038", |
| 16 | "#4b5563", |
| 17 | "#9aa2ad", |
| 18 | "#d7dbe0", |
| 19 | "#ffffff" |
| 20 | ] |
| 21 | }, |
| 22 | "surface": { |
| 23 | "role": "neutral", |
| 24 | "displayName": "Surface", |
| 25 | "canonical": "light-dark(#ffffff, #141518)", |
| 26 | "tonalRamp": [ |
| 27 | "#0e1116", |
| 28 | "#141820", |
| 29 | "#1a1f26", |
| 30 | "#2a3038", |
| 31 | "#525a66", |
| 32 | "#9aa2ad", |
| 33 | "#e3e6ea", |
| 34 | "#ffffff" |
| 35 | ] |
| 36 | }, |
| 37 | "surface-2": { |
| 38 | "role": "neutral", |
| 39 | "displayName": "Surface Sunken", |
| 40 | "canonical": "light-dark(#f3f3f5, #18191d)", |
| 41 | "tonalRamp": [ |
| 42 | "#0b0e12", |
| 43 | "#15191f", |
| 44 | "#1f242b", |
| 45 | "#2f353d", |
| 46 | "#565d67", |
| 47 | "#9ba2ab", |
| 48 | "#dfe2e5", |
| 49 | "#f6f7f8" |
| 50 | ] |
| 51 | }, |
| 52 | "line": { |
| 53 | "role": "neutral", |
| 54 | "displayName": "Hairline", |
| 55 | "canonical": "light-dark(#e6e6ea, #26282e)", |
| 56 | "tonalRamp": [ |
| 57 | "#14171c", |
| 58 | "#2a3038", |
| 59 | "#3b424c", |
| 60 | "#59616c", |
| 61 | "#7f8792", |
| 62 | "#adb3bc", |
| 63 | "#e5e7eb", |
| 64 | "#f7f8f9" |
| 65 | ] |
| 66 | }, |
| 67 | "line-strong": { |
| 68 | "role": "neutral", |
| 69 | "displayName": "Control Edge", |
| 70 | "canonical": "light-dark(#868691, #71737c)", |
| 71 | "tonalRamp": [ |
| 72 | "#181b20", |
| 73 | "#2b3038", |
| 74 | "#414852", |
| 75 | "#5b636e", |
| 76 | "#7f8792", |
| 77 | "#a5abb4", |
| 78 | "#cbcfd5", |
| 79 | "#eceef0" |
| 80 | ] |
| 81 | }, |
| 82 | "text": { |
| 83 | "role": "neutral", |
| 84 | "displayName": "Ink", |
| 85 | "canonical": "light-dark(#18181b, #ededf0)", |
| 86 | "tonalRamp": [ |
| 87 | "#0a0c0e", |
| 88 | "#1a1d21", |
| 89 | "#2e333a", |
| 90 | "#4b5563", |
| 91 | "#727a86", |
| 92 | "#a3aab4", |
| 93 | "#cfd4d9", |
| 94 | "#e8eaed" |
| 95 | ] |
| 96 | }, |
| 97 | "text-muted": { |
| 98 | "role": "neutral", |
| 99 | "displayName": "Ink Muted", |
| 100 | "canonical": "light-dark(#52525c, #a1a1aa)", |
| 101 | "tonalRamp": [ |
| 102 | "#12161b", |
| 103 | "#232a33", |
| 104 | "#374151", |
| 105 | "#4b5563", |
| 106 | "#6b7382", |
| 107 | "#949ca8", |
| 108 | "#c3c9d1", |
| 109 | "#e6e9ed" |
| 110 | ] |
| 111 | }, |
| 112 | "text-faint": { |
| 113 | "role": "neutral", |
| 114 | "displayName": "Ink Faint", |
| 115 | "canonical": "light-dark(#6b6b75, #8c8c96)", |
| 116 | "tonalRamp": [ |
| 117 | "#151920", |
| 118 | "#262c34", |
| 119 | "#3c434e", |
| 120 | "#535a66", |
| 121 | "#656c78", |
| 122 | "#98a1ac", |
| 123 | "#c6cbd2", |
| 124 | "#e9ebee" |
| 125 | ] |
| 126 | }, |
| 127 | "accent": { |
| 128 | "role": "primary", |
| 129 | "displayName": "Interface Blue", |
| 130 | "canonical": "light-dark(#1f56d6, #7aa2ff)", |
| 131 | "tonalRamp": [ |
| 132 | "#0a1b3f", |
| 133 | "#102a63", |
| 134 | "#173d8f", |
| 135 | "#1a4fc0", |
| 136 | "#2464e0", |
| 137 | "#5b8bee", |
| 138 | "#7ba7ff", |
| 139 | "#eef3fe" |
| 140 | ] |
| 141 | }, |
| 142 | "accent-strong": { |
| 143 | "role": "primary", |
| 144 | "displayName": "Interface Blue Deep", |
| 145 | "canonical": "light-dark(#1844ac, #a3c0ff)", |
| 146 | "tonalRamp": [ |
| 147 | "#08152f", |
| 148 | "#0e2354", |
| 149 | "#143a86", |
| 150 | "#1a4fc0", |
| 151 | "#3a6ddc", |
| 152 | "#6e97f2", |
| 153 | "#a3c0ff", |
| 154 | "#e4ecff" |
| 155 | ] |
| 156 | }, |
| 157 | "accent-soft": { |
| 158 | "role": "primary", |
| 159 | "displayName": "Interface Blue Wash", |
| 160 | "canonical": "light-dark(#ecf1ff, #16234a)", |
| 161 | "tonalRamp": [ |
| 162 | "#0c1526", |
| 163 | "#17233a", |
| 164 | "#22334f", |
| 165 | "#33496b", |
| 166 | "#5d7cae", |
| 167 | "#93a9cf", |
| 168 | "#cbd9f4", |
| 169 | "#eef3fe" |
| 170 | ] |
| 171 | }, |
| 172 | "on-accent": { |
| 173 | "role": "primary", |
| 174 | "displayName": "On Accent", |
| 175 | "canonical": "light-dark(#ffffff, #0c0d0f)", |
| 176 | "tonalRamp": [ |
| 177 | "#0b1220", |
| 178 | "#16203a", |
| 179 | "#293c5a", |
| 180 | "#475874", |
| 181 | "#7386a3", |
| 182 | "#a6b4c9", |
| 183 | "#d3dae5", |
| 184 | "#ffffff" |
| 185 | ] |
| 186 | }, |
| 187 | "accent-fill": { |
| 188 | "role": "primary", |
| 189 | "displayName": "Accent Fill", |
| 190 | "canonical": "light-dark(#2257df, #2f61e8)" |
| 191 | }, |
| 192 | "on-accent-fill": { |
| 193 | "role": "primary", |
| 194 | "displayName": "On Accent Fill", |
| 195 | "canonical": "#ffffff" |
| 196 | }, |
| 197 | "danger-fill": { |
| 198 | "role": "primary", |
| 199 | "displayName": "Danger Fill", |
| 200 | "canonical": "light-dark(#c62f2f, #c43232)" |
| 201 | }, |
| 202 | "control-border": { |
| 203 | "role": "neutral", |
| 204 | "displayName": "Control Border", |
| 205 | "canonical": "light-dark(#d0d4dc, #343843)" |
| 206 | }, |
| 207 | "tone-neutral-fg": { |
| 208 | "role": "tertiary", |
| 209 | "displayName": "Neutral Tone Ink", |
| 210 | "canonical": "light-dark(#374151, #c3c9d1)", |
| 211 | "tonalRamp": [ |
| 212 | "#0f1318", |
| 213 | "#1c222a", |
| 214 | "#293240", |
| 215 | "#374151", |
| 216 | "#5b6675", |
| 217 | "#8b95a3", |
| 218 | "#c3c9d1", |
| 219 | "#eef0f2" |
| 220 | ] |
| 221 | }, |
| 222 | "tone-neutral-bg": { |
| 223 | "role": "tertiary", |
| 224 | "displayName": "Neutral Tone Ground", |
| 225 | "canonical": "light-dark(#eef0f2, #262c34)", |
| 226 | "tonalRamp": [ |
| 227 | "#12151a", |
| 228 | "#262c34", |
| 229 | "#39414c", |
| 230 | "#525b68", |
| 231 | "#78818e", |
| 232 | "#a6adb7", |
| 233 | "#d2d7dc", |
| 234 | "#eef0f2" |
| 235 | ] |
| 236 | }, |
| 237 | "tone-positive-fg": { |
| 238 | "role": "tertiary", |
| 239 | "displayName": "Positive Tone Ink", |
| 240 | "canonical": "light-dark(#166534, #6ee7a8)", |
| 241 | "tonalRamp": [ |
| 242 | "#04170c", |
| 243 | "#0a2e18", |
| 244 | "#0f4724", |
| 245 | "#166534", |
| 246 | "#22874a", |
| 247 | "#3fb673", |
| 248 | "#6ee7a8", |
| 249 | "#dcfce7" |
| 250 | ] |
| 251 | }, |
| 252 | "tone-positive-bg": { |
| 253 | "role": "tertiary", |
| 254 | "displayName": "Positive Tone Ground", |
| 255 | "canonical": "light-dark(#dcfce7, #10281c)", |
| 256 | "tonalRamp": [ |
| 257 | "#06150e", |
| 258 | "#10281c", |
| 259 | "#1b432e", |
| 260 | "#2a6647", |
| 261 | "#3f8f65", |
| 262 | "#6bbb8e", |
| 263 | "#a6dcbf", |
| 264 | "#dcfce7" |
| 265 | ] |
| 266 | }, |
| 267 | "tone-warning-fg": { |
| 268 | "role": "tertiary", |
| 269 | "displayName": "Warning Tone Ink", |
| 270 | "canonical": "light-dark(#854d0e, #f0c46a)", |
| 271 | "tonalRamp": [ |
| 272 | "#1c1104", |
| 273 | "#38230a", |
| 274 | "#573710", |
| 275 | "#854d0e", |
| 276 | "#b3741f", |
| 277 | "#d69f3f", |
| 278 | "#f0c46a", |
| 279 | "#fef3c7" |
| 280 | ] |
| 281 | }, |
| 282 | "tone-warning-bg": { |
| 283 | "role": "tertiary", |
| 284 | "displayName": "Warning Tone Ground", |
| 285 | "canonical": "light-dark(#fef3c7, #2c2110)", |
| 286 | "tonalRamp": [ |
| 287 | "#150f07", |
| 288 | "#2c2110", |
| 289 | "#46351a", |
| 290 | "#665029", |
| 291 | "#8f733f", |
| 292 | "#bda067", |
| 293 | "#e0cb9b", |
| 294 | "#fef3c7" |
| 295 | ] |
| 296 | }, |
| 297 | "tone-negative-fg": { |
| 298 | "role": "tertiary", |
| 299 | "displayName": "Negative Tone Ink", |
| 300 | "canonical": "light-dark(#b91c1c, #f79aa0)", |
| 301 | "tonalRamp": [ |
| 302 | "#210607", |
| 303 | "#420b0c", |
| 304 | "#6d1414", |
| 305 | "#b91c1c", |
| 306 | "#d64a48", |
| 307 | "#ea7377", |
| 308 | "#f79aa0", |
| 309 | "#fee2e2" |
| 310 | ] |
| 311 | }, |
| 312 | "tone-negative-bg": { |
| 313 | "role": "tertiary", |
| 314 | "displayName": "Negative Tone Ground", |
| 315 | "canonical": "light-dark(#fee2e2, #331819)", |
| 316 | "tonalRamp": [ |
| 317 | "#190b0c", |
| 318 | "#331819", |
| 319 | "#502527", |
| 320 | "#73393b", |
| 321 | "#9c5c5e", |
| 322 | "#c58a8c", |
| 323 | "#e6b8ba", |
| 324 | "#fee2e2" |
| 325 | ] |
| 326 | } |
| 327 | }, |
| 328 | "typographyMeta": { |
| 329 | "display": { |
| 330 | "displayName": "Display", |
| 331 | "purpose": "The lead reading in a stat band, and nothing else. The only genuinely large type in the system." |
| 332 | }, |
| 333 | "headline": { |
| 334 | "displayName": "Headline", |
| 335 | "purpose": "A screen's h1 in the page header. 1.25rem in day, 1.375rem in plain and signal; capped at 1.25rem under 34rem." |
| 336 | }, |
| 337 | "field": { |
| 338 | "displayName": "Field", |
| 339 | "purpose": "The form's headline input — a title, a name. The only control that leaves the body step, and the one exception to the Weight-Not-Size Rule, because what grows is the reader's own words rather than chrome." |
| 340 | }, |
| 341 | "title": { |
| 342 | "displayName": "Title", |
| 343 | "purpose": "Section headings in a card head, button labels, a row's name. Body-sized; separates by weight." |
| 344 | }, |
| 345 | "body": { |
| 346 | "displayName": "Body", |
| 347 | "purpose": "Everything else. 14px base. Subheads cap at 44rem." |
| 348 | }, |
| 349 | "label": { |
| 350 | "displayName": "Label", |
| 351 | "purpose": "The eyebrow over a value: stat labels, column heads, rail section names. Sentence case in day, uppercase in plain and signal." |
| 352 | }, |
| 353 | "stat": { |
| 354 | "displayName": "Stat", |
| 355 | "purpose": "A stat's number when it is not the lead. A role because a dashboard uses it as one." |
| 356 | }, |
| 357 | "mono": { |
| 358 | "displayName": "Mono", |
| 359 | "purpose": "Reference codes, identifiers and machine values, on the rst-mono utility. A real second family, so they read as things to copy rather than to read." |
| 360 | } |
| 361 | }, |
| 362 | "themes": [ |
| 363 | { |
| 364 | "name": "day", |
| 365 | "role": "default", |
| 366 | "font": "system UI stack", |
| 367 | "radius": "6px / 4px", |
| 368 | "accent": "light-dark(#1f56d6, #7aa2ff)", |
| 369 | "headerRule": "var(--rst-line)", |
| 370 | "character": "Crisp and lit from above: near-greyscale neutrals, cobalt for anything that acts, natural depth, shadows lit by the hour and a sheen under the pointer." |
| 371 | }, |
| 372 | { |
| 373 | "name": "plain", |
| 374 | "role": "alternate", |
| 375 | "font": "system UI stack", |
| 376 | "radius": "4px / 3px", |
| 377 | "accent": "light-dark(#111111, #f2f2f2)", |
| 378 | "headerRule": "var(--rst-line)", |
| 379 | "character": "Nearly square, near-black accent, one hairline shadow layer." |
| 380 | }, |
| 381 | { |
| 382 | "name": "signal", |
| 383 | "role": "alternate", |
| 384 | "font": "Helvetica Neue stack", |
| 385 | "radius": "4px / 2px", |
| 386 | "accent": "light-dark(#1a56ff, #6d90ff)", |
| 387 | "headerRule": "color-mix(in oklab, var(--rst-accent) 45%, var(--rst-line))", |
| 388 | "character": "Milled: short dense shadows, electric blue, tightest corners." |
| 389 | } |
| 390 | ], |
| 391 | "shadows": [ |
| 392 | { |
| 393 | "name": "pop", |
| 394 | "value": "0 2px 4px light-dark(rgba(16, 24, 40, 0.06), rgba(0, 0, 0, 0.35)), 0 8px 16px -4px light-dark(rgba(16, 24, 40, 0.1), rgba(0, 0, 0, 0.5))", |
| 395 | "purpose": "Surfaces that float over the page: an open menu, a dropdown panel, a modal. Real elevation, kept tight. Theme-owned; signal uses 0 4px 10px and plain 0 1px 3px." |
| 396 | }, |
| 397 | { |
| 398 | "name": "knob", |
| 399 | "value": "0 1px 2px light-dark(rgba(16, 24, 40, 0.18), rgba(0, 0, 0, 0.4))", |
| 400 | "purpose": "The moving part of a control, such as a switch's knob." |
| 401 | }, |
| 402 | { |
| 403 | "name": "lift", |
| 404 | "value": "0 1px 2px light-dark(rgba(16, 24, 40, 0.08), rgba(0, 0, 0, 0.3))", |
| 405 | "purpose": "Barely-there separation on a raised strip." |
| 406 | }, |
| 407 | { |
| 408 | "name": "overlay", |
| 409 | "value": "light-dark(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.6))", |
| 410 | "purpose": "The modal scrim. Deeper in dark because black reads lighter against a dark backdrop." |
| 411 | } |
| 412 | ], |
| 413 | "motion": [ |
| 414 | { |
| 415 | "name": "reduced-motion", |
| 416 | "value": "@media (prefers-reduced-motion: reduce)", |
| 417 | "purpose": "Honoured throughout; animated affordances fall back to a static treatment rather than disappearing." |
| 418 | } |
| 419 | ], |
| 420 | "breakpoints": [ |
| 421 | { |
| 422 | "name": "reflow-floor", |
| 423 | "value": "320px" |
| 424 | }, |
| 425 | { |
| 426 | "name": "header-stack", |
| 427 | "value": "34rem" |
| 428 | }, |
| 429 | { |
| 430 | "name": "rail-collapse", |
| 431 | "value": "800px" |
| 432 | }, |
| 433 | { |
| 434 | "name": "page-column-cap", |
| 435 | "value": "64rem" |
| 436 | } |
| 437 | ] |
| 438 | }, |
| 439 | "components": [ |
| 440 | { |
| 441 | "name": "Primary Button", |
| 442 | "kind": "button", |
| 443 | "refersTo": "button-primary", |
| 444 | "description": "The one action a screen most wants taken. One per screen head.", |
| 445 | "html": "<a class=\"ds-btn ds-btn-primary\" href=\"#\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 12h14\"/><path d=\"M12 5v14\"/></svg>Write a post</a>", |
| 446 | "css": ".ds-btn { align-items: center; background: var(--rst-surface, #ffffff); border: 1px solid var(--rst-line-strong, #7f8792); border-radius: var(--rst-radius-sm, 6px); color: var(--rst-text, #1a1d21); cursor: pointer; display: inline-flex; font-family: inherit; font-size: 0.78125rem; font-weight: 600; gap: 0.5rem; line-height: 1.4; padding: 0.45rem 0.75rem; text-decoration: none; } .ds-btn svg { block-size: 1em; inline-size: 1em; flex: none; } .ds-btn-primary { background: var(--rst-accent, #2464e0); border-color: var(--rst-accent, #2464e0); color: var(--rst-on-accent, #ffffff); } .ds-btn-primary:hover { background: var(--rst-accent-strong, #1a4fc0); border-color: var(--rst-accent-strong, #1a4fc0); } .ds-btn-primary:focus-visible { outline: 2px solid var(--rst-accent, #2464e0); outline-offset: 2px; }" |
| 447 | }, |
| 448 | { |
| 449 | "name": "Default Button", |
| 450 | "kind": "button", |
| 451 | "refersTo": "button", |
| 452 | "description": "Every other action. Hover moves the border and label to the accent rather than filling.", |
| 453 | "html": "<a class=\"ds-btn ds-btn-default\" href=\"#\">Edit</a>", |
| 454 | "css": ".ds-btn-default { align-items: center; background: var(--rst-surface, #ffffff); border: 1px solid var(--rst-line-strong, #7f8792); border-radius: var(--rst-radius-sm, 6px); color: var(--rst-text, #1a1d21); cursor: pointer; display: inline-flex; font-family: inherit; font-size: 0.78125rem; font-weight: 600; gap: 0.5rem; line-height: 1.4; padding: 0.45rem 0.75rem; text-decoration: none; } .ds-btn-default:hover { border-color: var(--rst-accent, #2464e0); color: var(--rst-accent, #2464e0); } .ds-btn-default:focus-visible { outline: 2px solid var(--rst-accent, #2464e0); outline-offset: 2px; }" |
| 455 | }, |
| 456 | { |
| 457 | "name": "Text Input", |
| 458 | "kind": "input", |
| 459 | "refersTo": "input", |
| 460 | "description": "A labelled field with its hint. Focus is an offset outline, never a border swap, so focus never resizes the control.", |
| 461 | "html": "<div class=\"ds-field\"><label class=\"ds-field-label\" for=\"ds-title\">Title</label><input class=\"ds-input\" id=\"ds-title\" type=\"text\" value=\"Release notes for v0.24\" aria-describedby=\"ds-title-help\"><small class=\"ds-field-help\" id=\"ds-title-help\">Shown at the top of the post.</small></div>", |
| 462 | "css": ".ds-field { display: block; margin: 0 0 1rem; } .ds-field-label { color: var(--rst-text, #1a1d21); display: block; font-size: 0.78125rem; font-weight: 600; margin: 0 0 0.2rem; } .ds-input { background: var(--rst-surface, #ffffff); border: 1px solid var(--rst-line-strong, #7f8792); border-radius: var(--rst-radius-sm, 6px); box-sizing: border-box; color: var(--rst-text, #1a1d21); font: inherit; font-size: 0.875rem; padding: 0.45rem 0.6rem; width: 100%; } .ds-input:focus-visible { outline: 2px solid var(--rst-accent, #2464e0); outline-offset: 2px; } .ds-field-help { color: var(--rst-text-muted, #4b5563); display: block; font-size: 0.71875rem; margin: 0.45rem 0 0; }" |
| 463 | }, |
| 464 | { |
| 465 | "name": "Status Pill", |
| 466 | "kind": "chip", |
| 467 | "refersTo": "status-pill", |
| 468 | "description": "A record's status. Always renders its label; the dot is decoration on top of the word, never a substitute.", |
| 469 | "html": "<span class=\"ds-pill ds-pill-warning\">Waiting</span> <span class=\"ds-pill ds-pill-positive\">Resolved</span> <span class=\"ds-pill\">Open</span>", |
| 470 | "css": ".ds-pill { align-items: center; background: var(--rst-tone-neutral-bg, #eef0f2); border-radius: 999px; color: var(--rst-tone-neutral-fg, #374151); display: inline-flex; font-size: 0.71875rem; font-weight: 600; gap: 0.35rem; padding: 0.15rem 0.5rem; } .ds-pill::before { background: currentColor; border-radius: 50%; block-size: 0.4em; content: \"\"; inline-size: 0.4em; } .ds-pill-positive { background: var(--rst-tone-positive-bg, #dcfce7); color: var(--rst-tone-positive-fg, #166534); } .ds-pill-warning { background: var(--rst-tone-warning-bg, #fef3c7); color: var(--rst-tone-warning-fg, #854d0e); } .ds-pill-negative { background: var(--rst-tone-negative-bg, #fee2e2); color: var(--rst-tone-negative-fg, #b91c1c); }" |
| 471 | }, |
| 472 | { |
| 473 | "name": "Card", |
| 474 | "kind": "card", |
| 475 | "refersTo": "card", |
| 476 | "description": "The padded section card, with its heading sitting outside it.", |
| 477 | "html": "<div class=\"ds-box-head\"><h2>Payout</h2><a class=\"ds-btn-default\" href=\"#\">Edit</a></div><section class=\"ds-box\"><p>Everything on a screen sits inside boxes.</p><div class=\"ds-box-foot\">Last updated 2 hours ago</div></section>", |
| 478 | "css": ".ds-box { background: var(--rst-surface, #ffffff); border: 1px solid var(--rst-line, #e5e7eb); border-radius: var(--rst-radius, 8px); color: var(--rst-text, #1a1d21); font-size: 0.875rem; line-height: 1.6; margin: 0; padding: 1.1rem 1.25rem; } .ds-box > :first-child { margin-top: 0; } .ds-box > :last-child { margin-bottom: 0; } .ds-box-head { align-items: baseline; display: flex; gap: 1rem; justify-content: space-between; margin: 0 0 0.5rem; } .ds-box-head h2 { color: var(--rst-text, #1a1d21); font-size: 0.875rem; font-weight: 600; margin: 0; } .ds-box-foot { border-top: 1px solid var(--rst-line, #e5e7eb); color: var(--rst-text-muted, #4b5563); font-size: 0.78125rem; margin-top: 1rem; padding-top: 0.75rem; }" |
| 479 | }, |
| 480 | { |
| 481 | "name": "Navigation Rail", |
| 482 | "kind": "nav", |
| 483 | "refersTo": null, |
| 484 | "description": "The sidebar shell's rail. The current item is marked by three signals at once — tint, colour and weight — plus aria-current in the markup.", |
| 485 | "html": "<nav class=\"ds-nav\" aria-label=\"Main\"><a href=\"#\" aria-current=\"true\">Dashboard</a><a href=\"#\">Requests</a><a href=\"#\">Settings</a></nav>", |
| 486 | "css": ".ds-nav { display: flex; flex-direction: column; gap: 0.1rem; inline-size: 220px; padding: 0.5rem; } .ds-nav a { border-radius: var(--rst-radius-sm, 6px); color: var(--rst-text-muted, #4b5563); display: block; font-size: 0.875rem; padding: 0.4rem 0.6rem; text-decoration: none; } .ds-nav a:hover { background: var(--rst-surface-2, #f6f7f8); color: var(--rst-text, #1a1d21); } .ds-nav a[aria-current] { background: var(--rst-accent-soft, #eef3fe); color: var(--rst-accent, #2464e0); font-weight: 600; } .ds-nav a:focus-visible { outline: 2px solid var(--rst-accent, #2464e0); outline-offset: 2px; }" |
| 487 | }, |
| 488 | { |
| 489 | "name": "Stat Band", |
| 490 | "kind": "custom", |
| 491 | "refersTo": null, |
| 492 | "description": "The instrument strip a dashboard opens with. One cell may be the lead; the change carries its own sign, and its tone is supplied rather than derived.", |
| 493 | "html": "<div class=\"ds-stats\"><div class=\"ds-stat ds-stat-lead\"><span class=\"ds-stat-label\">Revenue this month</span><span class=\"ds-stat-num\">€48,210</span><span class=\"ds-stat-delta ds-stat-up\">+12%</span><span class=\"ds-stat-note\">vs. last month</span></div><div class=\"ds-stat\"><span class=\"ds-stat-label\">Orders today</span><span class=\"ds-stat-num\">318</span></div><div class=\"ds-stat\"><span class=\"ds-stat-label\">Average basket</span><span class=\"ds-stat-num\">€41.20</span><span class=\"ds-stat-delta ds-stat-down\">−3%</span></div></div>", |
| 494 | "css": ".ds-stats { background: var(--rst-surface, #ffffff); border: 1px solid var(--rst-line, #e5e7eb); border-radius: var(--rst-radius, 8px); display: flex; flex-wrap: wrap; } .ds-stat { border-inline-start: 1px solid var(--rst-line, #e5e7eb); display: flex; flex: 1 1 9rem; flex-direction: column; gap: 0.15rem; min-inline-size: 0; padding: 1rem; } .ds-stat:first-child { border-inline-start: 0; } .ds-stat-lead { flex-basis: 14rem; } .ds-stat-label { color: var(--rst-text-muted, #4b5563); font-size: 0.71875rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; } .ds-stat-num { color: var(--rst-text, #1a1d21); font-size: 1.5rem; font-variant-numeric: tabular-nums; font-weight: 650; line-height: 1.1; } .ds-stat-lead .ds-stat-num { font-size: 2.1rem; } .ds-stat-delta { font-size: 0.78125rem; font-variant-numeric: tabular-nums; font-weight: 600; } .ds-stat-up { color: var(--rst-tone-positive-fg, #166534); } .ds-stat-down { color: var(--rst-tone-negative-fg, #b91c1c); } .ds-stat-note { color: var(--rst-text-faint, #656c78); font-size: 0.71875rem; }" |
| 495 | } |
| 496 | ], |
| 497 | "narrative": { |
| 498 | "northStar": "A Default With a Point of View", |
| 499 | "overview": "This is the interface an application has before anyone has styled it, and it should look like someone chose it. day is near-greyscale with hairline borders, one cobalt for anything that acts, crisp corners, and natural depth: light comes from above, cards rest on the page, controls press in and slowly lift. It aims to read as professional, in the family of the tools people already trust, and to be recognisably this system's.\n\nRemixability is still the thesis. Every colour, radius, shadow, highlight and strength resolves through a custom property, and the three shipped themes prove the axes work rather than offering a menu of three answers: day turns depth on, plain and signal turn it off and render as they always did. An app that wants a fourth writes one :root block. Files are delivered once and app-owned from that moment: edit them, or delete what you do not use.\n\nThe system is deliberately under-designed at the component level so an app's own character can land on top without a fight. It is explicitly not the SaaS gradient template — no purple-to-blue washes, glassmorphism, oversized rounded cards or decorative blobs — and explicitly not a vendor-branded admin kit that stamps someone else's identity on an app and resists removal. Both hand you an identity you did not choose, which is the one thing this system exists not to do.", |
| 500 | "keyCharacteristics": [ |
| 501 | "Small, dense type: a 14px base with 12.5px and 11.5px steps below it.", |
| 502 | "Near-flat surfaces separated by 1px hairlines; depth is a theme axis.", |
| 503 | "One accent, carrying action and position; status carries its own four-tone palette.", |
| 504 | "Every colour is a light-dark() pair, so light and dark are one declaration rather than two stylesheets.", |
| 505 | "Works with JavaScript off. Scripts are enhancement and each is deletable on its own." |
| 506 | ], |
| 507 | "rules": [ |
| 508 | { |
| 509 | "name": "The Gated Pair Rule", |
| 510 | "body": "A colour that carries text owes 4.5:1 against the surface it actually sits on, and a control boundary owes 3:1 — and owes it in every theme and both schemes, in the contrast gate, before it ships. Adding a colour means adding its pair to that gate. A tone chosen against a tinted ground says nothing about the same tone on a plain card.", |
| 511 | "section": "colors" |
| 512 | }, |
| 513 | { |
| 514 | "name": "The Two-Signal Rule", |
| 515 | "body": "State is never colour alone. A status pill renders its label, a meter prints its fraction as text beside the bar, and a stat's change carries its own + or − sign. Colour is the second signal, never the first.", |
| 516 | "section": "colors" |
| 517 | }, |
| 518 | { |
| 519 | "name": "The Light-Dark Rule", |
| 520 | "body": "Every colour is declared once as light-dark(<light>, <dark>) under color-scheme: light dark. There is no dark stylesheet and no second palette to keep in step; an explicit theme choice re-resolves the same declarations by setting color-scheme.", |
| 521 | "section": "colors" |
| 522 | }, |
| 523 | { |
| 524 | "name": "The Small-Base Rule", |
| 525 | "body": "The base is 14px, not 16px, and every step is declared in rem so the whole scale tracks a reader who has raised their browser default. Do not convert any of it to px.", |
| 526 | "section": "typography" |
| 527 | }, |
| 528 | { |
| 529 | "name": "The Weight-Not-Size Rule", |
| 530 | "body": "Hierarchy inside a screen is carried by weight and colour before size. A card's h2 is body-sized at 600; reaching for a larger size to make a section feel important is how this system loses its density.", |
| 531 | "section": "typography" |
| 532 | }, |
| 533 | { |
| 534 | "name": "The Derived Rule Rule", |
| 535 | "body": "The page header's underline is not authored per theme: it is color-mix(in oklab, var(--rst-accent) N%, var(--rst-line)), mixed at 45% in signal, and left as the plain hairline in plain and in day, which keeps its cobalt for things that act. A theme that changes its accent gets a matching rule with no second value to keep in step. It is decorative and carries no contrast floor.", |
| 536 | "section": "shapes" |
| 537 | }, |
| 538 | { |
| 539 | "name": "The Grouped Number Rule", |
| 540 | "body": "Every number a person reads as a quantity is grouped for their locale — 54,173 in English, 54.173 in German, ٥٤٬١٧٣ in Arabic. Never a bare run of digits. The exception is exact: an identifier is not a quantity. Reference codes, order numbers, years, versions and port numbers are labels made of digits, and grouping one changes what it appears to be. Ruled 2026-09-02 and not yet met: form.FormatCents writes $1284.50 today.", |
| 541 | "section": "typography" |
| 542 | } |
| 543 | ], |
| 544 | "dos": [ |
| 545 | "Do change the system by setting custom properties. Every colour, radius, shadow and type family resolves through one; a fourth theme is a single :root block.", |
| 546 | "Do declare colours as light-dark(<light>, <dark>) pairs under color-scheme: light dark, so light and dark stay one declaration.", |
| 547 | "Do add any new colour that carries text to the contrast gate, at 4.5:1 against the surface it actually sits on, in all three themes and both schemes.", |
| 548 | "Do give state a second signal besides colour — a label, a sign, a number as text.", |
| 549 | "Do keep type in rem and the base at 14px, so the scale tracks a reader's own browser setting.", |
| 550 | "Do use logical properties (border-inline-start, margin-block-end) so borders and dividers land correctly in Arabic.", |
| 551 | "Do let the scriptless rendering be the real one; add script only as enhancement that can be deleted.", |
| 552 | "Do group the digits of any number a person reads as a quantity, for their locale — and never group an identifier, a year or a version." |
| 553 | ], |
| 554 | "donts": [ |
| 555 | "Don't reach for the SaaS gradient template — purple-to-blue washes, glassmorphism, oversized rounded cards, decorative blobs.", |
| 556 | "Don't stamp a vendor kit's identity onto an app. This system is under-designed on purpose so the app's own character can land on top.", |
| 557 | "Don't use the accent as decoration or mood. It marks what you can act on and where you are; status has its own four tones.", |
| 558 | "Don't float a resting surface. A card rests on the page with the theme's ambient light; a pop shadow means something has left the page plane.", |
| 559 | "Don't make a section feel important by making it bigger. Weight and colour carry hierarchy before size; a card's h2 is body-sized.", |
| 560 | "Don't shrink control padding without re-measuring against WCAG 2.2's 24px target-size minimum.", |
| 561 | "Don't compose a heading, a paragraph and a button side by side in a flex row. Screens stack vertically; horizontal arrangement is reserved for the idioms that ship it." |
| 562 | ] |
| 563 | } |
| 564 | } |
| 565 | |