@ponchia/ui 0.6.11 → 0.7.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/MIGRATIONS.json +14 -0
  3. package/README.md +14 -6
  4. package/behaviors/dialog.d.ts.map +1 -1
  5. package/behaviors/dialog.js +14 -0
  6. package/behaviors/modal.d.ts +17 -13
  7. package/behaviors/modal.d.ts.map +1 -1
  8. package/behaviors/modal.js +281 -106
  9. package/behaviors/popover.d.ts.map +1 -1
  10. package/behaviors/popover.js +50 -2
  11. package/behaviors/splitter.d.ts +2 -0
  12. package/behaviors/splitter.d.ts.map +1 -1
  13. package/behaviors/splitter.js +15 -1
  14. package/behaviors/theme.d.ts +3 -2
  15. package/behaviors/theme.d.ts.map +1 -1
  16. package/behaviors/theme.js +10 -6
  17. package/bin/bronto-ui-check.mjs +286 -0
  18. package/classes/classes.json +7 -0
  19. package/classes/vscode.css-custom-data.json +1 -1
  20. package/css/disclosure.css +10 -0
  21. package/css/dots.css +43 -18
  22. package/css/feedback.css +35 -0
  23. package/css/report.css +0 -40
  24. package/css/site.css +10 -0
  25. package/css/tokens.css +1 -1
  26. package/dist/bronto.css +1 -1
  27. package/dist/css/disclosure.css +1 -1
  28. package/dist/css/dots.css +1 -1
  29. package/dist/css/feedback.css +1 -1
  30. package/dist/css/report-kit.css +1 -1
  31. package/dist/css/report.css +1 -1
  32. package/dist/css/site.css +1 -1
  33. package/dist/css/tokens.css +1 -1
  34. package/docs/adr/0004-prune-unused-adapters.md +34 -0
  35. package/docs/architecture.md +15 -11
  36. package/docs/command.md +18 -4
  37. package/docs/migrations/0.6-to-0.7.md +85 -0
  38. package/docs/package-contract.md +10 -5
  39. package/docs/reference.md +1 -1
  40. package/docs/reporting.md +8 -8
  41. package/docs/stability.md +36 -10
  42. package/docs/theming.md +49 -5
  43. package/docs/usage.md +68 -18
  44. package/docs/workbench.md +16 -2
  45. package/llms.txt +7 -3
  46. package/package.json +13 -3
  47. package/qwik/index.d.ts.map +1 -1
  48. package/qwik/index.js +4 -0
  49. package/react/index.d.ts.map +1 -1
  50. package/react/index.js +4 -0
  51. package/solid/index.d.ts.map +1 -1
  52. package/solid/index.js +4 -0
  53. package/svelte/index.d.ts.map +1 -1
  54. package/svelte/index.js +4 -0
  55. package/tokens/figma.variables.json +2 -2
  56. package/tokens/index.js +1 -1
  57. package/tokens/index.json +2 -2
  58. package/tokens/resolved.json +1 -1
  59. package/tokens/tokens.dtcg.json +2508 -399
  60. package/vue/index.d.ts.map +1 -1
  61. package/vue/index.js +4 -0
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ /** Validate literal Bronto classes and CSS custom-property references in a consumer. */
3
+ import { readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
4
+ import { extname, resolve, relative, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
+ const classesManifest = JSON.parse(
9
+ readFileSync(resolve(packageRoot, 'classes/classes.json'), 'utf8'),
10
+ );
11
+ const tokensManifest = JSON.parse(readFileSync(resolve(packageRoot, 'tokens/index.json'), 'utf8'));
12
+
13
+ const SOURCE_EXTENSIONS = new Set([
14
+ '.astro',
15
+ '.css',
16
+ '.html',
17
+ '.js',
18
+ '.jsx',
19
+ '.mjs',
20
+ '.svelte',
21
+ '.ts',
22
+ '.tsx',
23
+ '.vue',
24
+ ]);
25
+ const SKIP_DIRS = new Set([
26
+ '.git',
27
+ '.astro',
28
+ '.next',
29
+ '.nuxt',
30
+ '.output',
31
+ '.pytest_cache',
32
+ '.svelte-kit',
33
+ '.tox',
34
+ '.vercel',
35
+ '.venv',
36
+ '.vite',
37
+ '__pycache__',
38
+ 'build',
39
+ 'coverage',
40
+ 'dist',
41
+ 'node_modules',
42
+ 'out',
43
+ 'playwright-report',
44
+ 'public',
45
+ 'storybook-static',
46
+ 'test-results',
47
+ 'vendor',
48
+ 'venv',
49
+ ]);
50
+
51
+ const knownClasses = new Set(classesManifest.classes);
52
+ // CSS generic font-family keywords share the ui-* prefix but are never class
53
+ // literals. Keep the scanner broad enough to catch JS-built class strings
54
+ // while excluding these standardized non-class identifiers.
55
+ const nonClassIdentifiers = new Set(['ui-monospace', 'ui-rounded', 'ui-sans-serif', 'ui-serif']);
56
+ const knownTokens = new Set(
57
+ Object.values(tokensManifest.cssVars).flatMap((group) => Object.keys(group)),
58
+ );
59
+ for (const property of classesManifest.customProperties) knownTokens.add(property.name);
60
+ for (const entry of readdirSync(resolve(packageRoot, 'dist/css'), { withFileTypes: true })) {
61
+ if (!entry.isFile() || extname(entry.name) !== '.css') continue;
62
+ const css = readFileSync(resolve(packageRoot, 'dist/css', entry.name), 'utf8');
63
+ for (const match of css.matchAll(/(--[a-z][\w-]*)\s*:/gi)) knownTokens.add(match[1]);
64
+ }
65
+ const reservedTokenPrefixes = new Set(
66
+ [...knownTokens].map((name) => `--${name.slice(2).split('-')[0]}`),
67
+ );
68
+ const LINE_COMMENT_EXTENSIONS = new Set([
69
+ '.astro',
70
+ '.html',
71
+ '.js',
72
+ '.jsx',
73
+ '.mjs',
74
+ '.svelte',
75
+ '.ts',
76
+ '.tsx',
77
+ '.vue',
78
+ ]);
79
+ const HTML_COMMENT_EXTENSIONS = new Set(['.astro', '.html', '.svelte', '.vue']);
80
+
81
+ const blankComment = (value) => value.replace(/[^\r\n]/g, ' ');
82
+
83
+ function quoteEnd(text, start, quote) {
84
+ for (let index = start + 1; index < text.length; index += 1) {
85
+ if (text[index] === '\\') index += 1;
86
+ else if (text[index] === quote) return index + 1;
87
+ }
88
+ return text.length;
89
+ }
90
+
91
+ function stripSlashComments(text, lineComments) {
92
+ let output = '';
93
+ let index = 0;
94
+ while (index < text.length) {
95
+ const char = text[index];
96
+ const next = text[index + 1];
97
+ if (char === '"' || char === "'" || char === '`') {
98
+ const end = quoteEnd(text, index, char);
99
+ output += text.slice(index, end);
100
+ index = end;
101
+ continue;
102
+ }
103
+ if (char === '/' && next === '*') {
104
+ const close = text.indexOf('*/', index + 2);
105
+ const end = close === -1 ? text.length : close + 2;
106
+ output += blankComment(text.slice(index, end));
107
+ index = end;
108
+ continue;
109
+ }
110
+ if (lineComments && char === '/' && next === '/') {
111
+ const newline = text.indexOf('\n', index + 2);
112
+ const end = newline === -1 ? text.length : newline;
113
+ output += blankComment(text.slice(index, end));
114
+ index = end;
115
+ continue;
116
+ }
117
+ output += char;
118
+ index += 1;
119
+ }
120
+ return output;
121
+ }
122
+
123
+ function sourceText(text, extension) {
124
+ const withoutHtml = HTML_COMMENT_EXTENSIONS.has(extension)
125
+ ? text.replace(/<!--[\s\S]*?-->/g, blankComment)
126
+ : text;
127
+ return stripSlashComments(withoutHtml, LINE_COMMENT_EXTENSIONS.has(extension));
128
+ }
129
+
130
+ function lineAt(text, index) {
131
+ return text.slice(0, index).split('\n').length;
132
+ }
133
+
134
+ function filesUnder(input) {
135
+ const absolute = resolve(input);
136
+ const stat = statSync(absolute);
137
+ if (stat.isFile()) return SOURCE_EXTENSIONS.has(extname(absolute)) ? [absolute] : [];
138
+ if (!stat.isDirectory()) return [];
139
+ const files = [];
140
+ for (const entry of readdirSync(absolute, { withFileTypes: true })) {
141
+ if (entry.isSymbolicLink()) continue;
142
+ const child = resolve(absolute, entry.name);
143
+ if (entry.isDirectory()) {
144
+ if (!SKIP_DIRS.has(entry.name)) files.push(...filesUnder(child));
145
+ } else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
146
+ files.push(child);
147
+ }
148
+ }
149
+ return files;
150
+ }
151
+
152
+ function tokenPrefix(name) {
153
+ return `--${name.slice(2).split('-')[0]}`;
154
+ }
155
+
156
+ export function checkPaths(inputs, { allowClasses = [], allowTokens = [] } = {}) {
157
+ const allowedClasses = new Set([...knownClasses, ...allowClasses]);
158
+ const allowedTokens = new Set([...knownTokens, ...allowTokens]);
159
+ const files = [...new Set(inputs.flatMap(filesUnder))].sort();
160
+ const documents = files.map((file) => {
161
+ const text = readFileSync(file, 'utf8');
162
+ return { file, text, scanned: sourceText(text, extname(file)) };
163
+ });
164
+ const locallyDefinedTokens = new Set();
165
+ for (const { scanned } of documents) {
166
+ for (const match of scanned.matchAll(/(--[a-z][\w-]*)\s*:/gi)) {
167
+ locallyDefinedTokens.add(match[1]);
168
+ }
169
+ }
170
+
171
+ const findings = [];
172
+ const seen = new Set();
173
+ const add = (finding) => {
174
+ const key = `${finding.kind}:${finding.file}:${finding.line}:${finding.name}`;
175
+ if (seen.has(key)) return;
176
+ seen.add(key);
177
+ findings.push(finding);
178
+ };
179
+
180
+ for (const { file, text, scanned } of documents) {
181
+ for (const match of scanned.matchAll(/(?<![/\w-])ui-[a-z0-9](?:[\w-]*[a-z0-9])?(?![\w/-])/gi)) {
182
+ if (!allowedClasses.has(match[0]) && !nonClassIdentifiers.has(match[0])) {
183
+ add({ kind: 'class', file, line: lineAt(text, match.index), name: match[0] });
184
+ }
185
+ }
186
+ for (const match of scanned.matchAll(/var\(\s*(--[a-z][\w-]*)/gi)) {
187
+ const name = match[1];
188
+ if (
189
+ reservedTokenPrefixes.has(tokenPrefix(name)) &&
190
+ !allowedTokens.has(name) &&
191
+ !locallyDefinedTokens.has(name)
192
+ ) {
193
+ add({ kind: 'token', file, line: lineAt(text, match.index), name });
194
+ }
195
+ }
196
+ }
197
+
198
+ return { files: files.length, findings };
199
+ }
200
+
201
+ function usage() {
202
+ return (
203
+ `Usage: bronto-ui-check [options] [path ...]\n\n` +
204
+ `Validate literal ui-* classes and unresolved Bronto-like var(--*) references.\n\n` +
205
+ `Options:\n` +
206
+ ` --allow-class NAME Allow one consumer-owned ui-* class (repeatable)\n` +
207
+ ` --allow-token NAME Allow one consumer-owned --* token (repeatable)\n` +
208
+ ` --json Print machine-readable JSON\n` +
209
+ ` --help Show this help\n`
210
+ );
211
+ }
212
+
213
+ function parseArgs(args) {
214
+ const options = { allowClasses: [], allowTokens: [], json: false, paths: [] };
215
+ for (let index = 0; index < args.length; index += 1) {
216
+ const arg = args[index];
217
+ if (arg === '--help') return { ...options, help: true };
218
+ if (arg === '--json') options.json = true;
219
+ else if (arg === '--allow-class') {
220
+ const value = args[++index];
221
+ if (!/^ui-[a-z0-9][\w-]*$/i.test(value || '')) {
222
+ throw new Error('--allow-class requires one ui-* class name');
223
+ }
224
+ options.allowClasses.push(value);
225
+ } else if (arg === '--allow-token') {
226
+ const value = args[++index];
227
+ if (!/^--[a-z][\w-]*$/i.test(value || '')) {
228
+ throw new Error('--allow-token requires one --* custom-property name');
229
+ }
230
+ options.allowTokens.push(value);
231
+ } else if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);
232
+ else options.paths.push(arg);
233
+ }
234
+ return options;
235
+ }
236
+
237
+ function main() {
238
+ let options;
239
+ try {
240
+ options = parseArgs(process.argv.slice(2));
241
+ } catch (error) {
242
+ console.error(error.message);
243
+ console.error(usage());
244
+ process.exitCode = 2;
245
+ return;
246
+ }
247
+ if (options.help) {
248
+ console.log(usage());
249
+ return;
250
+ }
251
+ const paths = options.paths.length ? options.paths : ['.'];
252
+ let result;
253
+ try {
254
+ result = checkPaths(paths, options);
255
+ } catch (error) {
256
+ console.error(`[bronto-ui-check] ${error.message}`);
257
+ process.exitCode = 2;
258
+ return;
259
+ }
260
+ const cwd = process.cwd();
261
+ const output = {
262
+ files: result.files,
263
+ findings: result.findings.map((finding) => ({
264
+ ...finding,
265
+ file: relative(cwd, finding.file) || '.',
266
+ })),
267
+ };
268
+ if (options.json) console.log(JSON.stringify(output, null, 2));
269
+ else if (output.findings.length) {
270
+ for (const finding of output.findings) {
271
+ console.error(
272
+ `${finding.file}:${finding.line} unknown Bronto ${finding.kind} ${finding.name}`,
273
+ );
274
+ }
275
+ console.error(
276
+ `[bronto-ui-check] ${output.findings.length} finding(s) in ${output.files} source file(s)`,
277
+ );
278
+ } else {
279
+ console.log(`[bronto-ui-check] ${output.files} source file(s) match the shipped contract`);
280
+ }
281
+ if (output.findings.length) process.exitCode = 1;
282
+ }
283
+
284
+ // npm installs bins as symlinks in node_modules/.bin. Compare their real path
285
+ // so the CLI runs both through that public entrypoint and by its package path.
286
+ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main();
@@ -2791,6 +2791,13 @@
2791
2791
  "behavior": "initSplitter",
2792
2792
  "note": "keyboard + pointer ARIA window-splitter behavior; updates --splitter-pos and aria-valuenow, then emits bronto:splitter:resize. The host owns persistence and pane state."
2793
2793
  },
2794
+ {
2795
+ "name": "data-bronto-splitter-adjust",
2796
+ "on": "a button inside a .ui-splitter host",
2797
+ "value": "signed percentage-point delta, for example -10 or 10",
2798
+ "behavior": "initSplitter",
2799
+ "note": "single-pointer non-drag alternative to moving the separator; activation clamps to aria-valuemin/aria-valuemax and emits bronto:splitter:resize."
2800
+ },
2794
2801
  {
2795
2802
  "name": "data-bronto-source-ref",
2796
2803
  "on": "a button/control that references a source card",
@@ -347,7 +347,7 @@
347
347
  },
348
348
  {
349
349
  "name": "--text-2xs",
350
- "description": "Global scale token. Value: `0.68rem`"
350
+ "description": "Global scale token. Value: `0.72rem`"
351
351
  },
352
352
  {
353
353
  "name": "--text-base",
@@ -272,6 +272,16 @@
272
272
  }
273
273
  }
274
274
 
275
+ @media (pointer: coarse) {
276
+ .ui-breadcrumb__item a {
277
+ align-items: center;
278
+ display: inline-flex;
279
+ justify-content: center;
280
+ min-block-size: max(24px, 1.6rem);
281
+ min-inline-size: max(24px, 1.6rem);
282
+ }
283
+ }
284
+
275
285
  /* --- Pagination --- */
276
286
 
277
287
  .ui-pagination {
package/css/dots.css CHANGED
@@ -136,7 +136,9 @@
136
136
 
137
137
  /* Status dot — the glyph-style state indicator. */
138
138
  .ui-dot {
139
- background: var(--text-dim);
139
+ --ui-dot-color: var(--text-dim);
140
+
141
+ background: var(--ui-dot-color);
140
142
  border-radius: 50%;
141
143
  display: inline-block;
142
144
  flex: 0 0 auto;
@@ -145,34 +147,45 @@
145
147
  }
146
148
 
147
149
  .ui-dot--accent {
148
- background: var(--accent);
150
+ --ui-dot-color: var(--accent);
149
151
  }
150
152
 
151
153
  .ui-dot--success {
152
- background: var(--success);
154
+ --ui-dot-color: var(--success);
153
155
  }
154
156
 
155
157
  .ui-dot--warning {
156
- background: var(--warning);
158
+ --ui-dot-color: var(--warning);
157
159
  }
158
160
 
159
161
  .ui-dot--danger {
160
- background: var(--danger);
162
+ --ui-dot-color: var(--danger);
161
163
  }
162
164
 
163
165
  .ui-dot--info {
164
- background: var(--info);
166
+ --ui-dot-color: var(--info);
165
167
  }
166
168
 
167
169
  .ui-dot--live {
168
- background: var(--success);
169
- box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
170
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--ui-dot-color) 70%, transparent);
170
171
  position: relative;
171
172
  }
172
173
 
174
+ /* Live describes motion, not status. A standalone live dot defaults to
175
+ success; an explicit tone remains the colour of both the dot and ring. */
176
+ .ui-dot--live:not(
177
+ .ui-dot--accent,
178
+ .ui-dot--success,
179
+ .ui-dot--warning,
180
+ .ui-dot--danger,
181
+ .ui-dot--info
182
+ ) {
183
+ --ui-dot-color: var(--success);
184
+ }
185
+
173
186
  .ui-dot--live::after {
174
187
  animation: pulseRing 1.8s var(--ease-out) infinite;
175
- border: 1px solid var(--success);
188
+ border: 1px solid var(--ui-dot-color);
176
189
  border-radius: 50%;
177
190
  content: '';
178
191
  inset: -3px;
@@ -562,36 +575,48 @@
562
575
  differentiable. */
563
576
  @media (forced-colors: active) {
564
577
  .ui-dot--success {
578
+ --ui-dot-color: LinkText;
579
+
565
580
  forced-color-adjust: none;
566
- background: LinkText;
567
581
  }
568
582
 
569
583
  .ui-dot--warning {
584
+ --ui-dot-color: Mark;
585
+
570
586
  forced-color-adjust: none;
571
- background: Mark;
572
587
  }
573
588
 
574
589
  .ui-dot--danger {
590
+ --ui-dot-color: Highlight;
591
+
575
592
  forced-color-adjust: none;
576
- background: Highlight;
577
593
  }
578
594
 
579
595
  .ui-dot--info {
596
+ --ui-dot-color: ButtonText;
597
+
580
598
  forced-color-adjust: none;
581
- background: ButtonText;
582
599
  }
583
600
 
584
601
  /* Brand/live dots aren't status tones, but they still encode meaning via
585
602
  background-color alone, which HCM flattens. Keep them on a distinct,
586
603
  opted-out system colour for completeness. */
587
- .ui-dot--accent,
588
- .ui-dot--live {
604
+ .ui-dot--accent {
605
+ --ui-dot-color: LinkText;
606
+
589
607
  forced-color-adjust: none;
590
- background: LinkText;
591
608
  }
592
609
 
593
- .ui-dot--live::after {
594
- border-color: LinkText;
610
+ .ui-dot--live:not(
611
+ .ui-dot--accent,
612
+ .ui-dot--success,
613
+ .ui-dot--warning,
614
+ .ui-dot--danger,
615
+ .ui-dot--info
616
+ ) {
617
+ --ui-dot-color: LinkText;
618
+
619
+ forced-color-adjust: none;
595
620
  }
596
621
 
597
622
  /* The masked one-node icon paints `background: currentcolor` through an SVG
package/css/feedback.css CHANGED
@@ -517,6 +517,41 @@
517
517
  background: var(--info);
518
518
  }
519
519
 
520
+ /* A labelled meter is core feedback grammar, not report-only layout. The
521
+ visible value remains the data of record because role=meter support varies. */
522
+ .ui-meter__row {
523
+ align-items: center;
524
+ display: grid;
525
+ gap: var(--space-2xs) var(--space-md);
526
+ grid-template-columns: minmax(9rem, 14rem) 1fr auto;
527
+ margin-block: var(--space-2xs);
528
+ }
529
+
530
+ .ui-meter__row .ui-meter {
531
+ min-inline-size: 8rem;
532
+ }
533
+
534
+ .ui-meter__label {
535
+ color: var(--text-soft);
536
+ }
537
+
538
+ .ui-meter__value {
539
+ color: var(--text);
540
+ font-family: var(--mono);
541
+ font-variant-numeric: tabular-nums;
542
+ text-align: end;
543
+ }
544
+
545
+ @media (max-width: 32rem) {
546
+ .ui-meter__row {
547
+ grid-template-columns: 1fr;
548
+ }
549
+
550
+ .ui-meter__value {
551
+ text-align: start;
552
+ }
553
+ }
554
+
520
555
  /* --- Steps — progress through a multi-step flow. Use an <ol>; the
521
556
  current step is aria-current="step" (no class), completed steps take
522
557
  --done. Counter-numbered markers, hairline connectors. --- */
package/css/report.css CHANGED
@@ -619,46 +619,6 @@
619
619
  text-transform: uppercase;
620
620
  }
621
621
 
622
- /* --- Labelled meter row ---
623
- A multi-meter block (SLO burn, error budgets, capacity) lays out as
624
- label | bar | value. The bare `ui-meter` base lives in feedback.css; this is
625
- the report-document grammar around it so authors stop hand-rolling the grid.
626
- The bar NEVER carries the reading alone (WCAG 1.4.1) — `ui-meter__value` is
627
- the data of record, and the bar clamps at 100 so an over-target figure still
628
- reads correctly in the value text. Collapses to a stack on a narrow screen. */
629
- .ui-meter__row {
630
- align-items: center;
631
- display: grid;
632
- gap: var(--space-2xs) var(--space-md);
633
- grid-template-columns: minmax(9rem, 14rem) 1fr auto;
634
- margin-block: var(--space-2xs);
635
- }
636
-
637
- .ui-meter__row .ui-meter {
638
- min-inline-size: 8rem;
639
- }
640
-
641
- .ui-meter__label {
642
- color: var(--text-soft);
643
- }
644
-
645
- .ui-meter__value {
646
- color: var(--text);
647
- font-family: var(--mono);
648
- font-variant-numeric: tabular-nums;
649
- text-align: end;
650
- }
651
-
652
- @media (max-width: 32rem) {
653
- .ui-meter__row {
654
- grid-template-columns: 1fr;
655
- }
656
-
657
- .ui-meter__value {
658
- text-align: start;
659
- }
660
- }
661
-
662
622
  /* A chart is NOT a bronto component — it needs scales + data binding, which the
663
623
  analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
664
624
  (docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
package/css/site.css CHANGED
@@ -296,6 +296,16 @@
296
296
  }
297
297
  }
298
298
 
299
+ @media (pointer: coarse) {
300
+ .ui-sitefooter__links a {
301
+ align-items: center;
302
+ display: inline-flex;
303
+ justify-content: center;
304
+ min-block-size: max(24px, 1.6rem);
305
+ min-inline-size: max(24px, 1.6rem);
306
+ }
307
+ }
308
+
299
309
  /* --- Tags — neutral, wrapping content labels (NOT ui-chip, which is a
300
310
  single interactive token). Use a <ul>. --- */
301
311
 
package/css/tokens.css CHANGED
@@ -33,7 +33,7 @@
33
33
  --display: var(--dot-font);
34
34
  --display-weight: 700;
35
35
  --display-weight-strong: 800;
36
- --text-2xs: 0.68rem;
36
+ --text-2xs: 0.72rem;
37
37
  --text-xs: 0.76rem;
38
38
  --text-sm: 0.86rem;
39
39
  --text-base: 0.95rem;