@dxos/ui-theme 0.8.4-staging.ac66bdf99f → 0.9.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 (169) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/index.mjs +89 -867
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/node-esm/index.mjs +89 -867
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/plugin/node-cjs/main.css +252 -119
  10. package/dist/plugin/node-cjs/main.css.map +3 -3
  11. package/dist/plugin/node-cjs/meta.json +1 -1
  12. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs +82 -10
  13. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs.map +3 -3
  14. package/dist/plugin/node-esm/main.css +252 -119
  15. package/dist/plugin/node-esm/main.css.map +3 -3
  16. package/dist/plugin/node-esm/meta.json +1 -1
  17. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs +84 -12
  18. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs.map +3 -3
  19. package/dist/types/src/Theme.stories.d.ts.map +1 -1
  20. package/dist/types/src/defs.d.ts +1 -1
  21. package/dist/types/src/defs.d.ts.map +1 -1
  22. package/dist/types/src/fragments/density.d.ts +2 -2
  23. package/dist/types/src/fragments/density.d.ts.map +1 -1
  24. package/dist/types/src/fragments/hover.d.ts +0 -1
  25. package/dist/types/src/fragments/hover.d.ts.map +1 -1
  26. package/dist/types/src/index.d.ts +0 -1
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/src/plugins/ThemePlugin.d.ts +3 -2
  29. package/dist/types/src/plugins/ThemePlugin.d.ts.map +1 -1
  30. package/dist/types/src/util/hash-styles.d.ts +12 -2
  31. package/dist/types/src/util/hash-styles.d.ts.map +1 -1
  32. package/dist/types/src/util/mx.d.ts +0 -36
  33. package/dist/types/src/util/mx.d.ts.map +1 -1
  34. package/dist/types/src/util/size.d.ts.map +1 -1
  35. package/dist/types/src/util/valence.d.ts +8 -2
  36. package/dist/types/src/util/valence.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +14 -21
  39. package/src/Theme.stories.tsx +94 -27
  40. package/src/css/DESIGN_SYSTEM.md +187 -0
  41. package/src/css/base/base.css +2 -2
  42. package/src/css/components/button.css +63 -13
  43. package/src/css/components/card.css +14 -0
  44. package/src/css/components/checkbox.css +3 -3
  45. package/src/css/components/focus.css +12 -12
  46. package/src/css/components/link.css +4 -1
  47. package/src/css/components/panel.css +45 -45
  48. package/src/css/components/state.css +99 -0
  49. package/src/css/components/state.md +101 -0
  50. package/src/css/components/surface.css +33 -11
  51. package/src/css/components/tag.css +25 -24
  52. package/src/css/integrations/codemirror.css +4 -3
  53. package/src/css/integrations/tldraw.css +1 -1
  54. package/src/css/layout/main.css +6 -0
  55. package/src/css/layout/size.css +16 -3
  56. package/src/css/theme/animation.css +31 -0
  57. package/src/css/theme/palette.css +34 -0
  58. package/src/css/theme/semantic.css +101 -60
  59. package/src/css/theme/spacing.css +29 -12
  60. package/src/css/theme/styles.css +172 -119
  61. package/src/css/utilities.css +42 -0
  62. package/src/defs.ts +3 -1
  63. package/src/fragments/AUDIT.md +0 -2
  64. package/src/fragments/density.ts +34 -7
  65. package/src/fragments/hover.ts +0 -2
  66. package/src/index.ts +1 -1
  67. package/src/main.css +68 -9
  68. package/src/plugins/ThemePlugin.ts +102 -14
  69. package/src/plugins/main.css +10 -7
  70. package/src/util/hash-styles.ts +54 -42
  71. package/src/util/mx.ts +1 -126
  72. package/src/util/valence.ts +15 -5
  73. package/dist/types/src/theme/components/avatar.d.ts +0 -21
  74. package/dist/types/src/theme/components/avatar.d.ts.map +0 -1
  75. package/dist/types/src/theme/components/breadcrumb.d.ts +0 -9
  76. package/dist/types/src/theme/components/breadcrumb.d.ts.map +0 -1
  77. package/dist/types/src/theme/components/button.d.ts +0 -15
  78. package/dist/types/src/theme/components/button.d.ts.map +0 -1
  79. package/dist/types/src/theme/components/card.d.ts +0 -12
  80. package/dist/types/src/theme/components/card.d.ts.map +0 -1
  81. package/dist/types/src/theme/components/dialog.d.ts +0 -17
  82. package/dist/types/src/theme/components/dialog.d.ts.map +0 -1
  83. package/dist/types/src/theme/components/focus.d.ts +0 -6
  84. package/dist/types/src/theme/components/focus.d.ts.map +0 -1
  85. package/dist/types/src/theme/components/icon-button.d.ts +0 -8
  86. package/dist/types/src/theme/components/icon-button.d.ts.map +0 -1
  87. package/dist/types/src/theme/components/icon.d.ts +0 -10
  88. package/dist/types/src/theme/components/icon.d.ts.map +0 -1
  89. package/dist/types/src/theme/components/index.d.ts +0 -27
  90. package/dist/types/src/theme/components/index.d.ts.map +0 -1
  91. package/dist/types/src/theme/components/input.d.ts +0 -115
  92. package/dist/types/src/theme/components/input.d.ts.map +0 -1
  93. package/dist/types/src/theme/components/link.d.ts +0 -7
  94. package/dist/types/src/theme/components/link.d.ts.map +0 -1
  95. package/dist/types/src/theme/components/list.d.ts +0 -14
  96. package/dist/types/src/theme/components/list.d.ts.map +0 -1
  97. package/dist/types/src/theme/components/main.d.ts +0 -28
  98. package/dist/types/src/theme/components/main.d.ts.map +0 -1
  99. package/dist/types/src/theme/components/menu.d.ts +0 -13
  100. package/dist/types/src/theme/components/menu.d.ts.map +0 -1
  101. package/dist/types/src/theme/components/message.d.ts +0 -12
  102. package/dist/types/src/theme/components/message.d.ts.map +0 -1
  103. package/dist/types/src/theme/components/popover.d.ts +0 -11
  104. package/dist/types/src/theme/components/popover.d.ts.map +0 -1
  105. package/dist/types/src/theme/components/scroll-area.d.ts +0 -32
  106. package/dist/types/src/theme/components/scroll-area.d.ts.map +0 -1
  107. package/dist/types/src/theme/components/select.d.ts +0 -13
  108. package/dist/types/src/theme/components/select.d.ts.map +0 -1
  109. package/dist/types/src/theme/components/separator.d.ts +0 -8
  110. package/dist/types/src/theme/components/separator.d.ts.map +0 -1
  111. package/dist/types/src/theme/components/skeleton.d.ts +0 -7
  112. package/dist/types/src/theme/components/skeleton.d.ts.map +0 -1
  113. package/dist/types/src/theme/components/splitter.d.ts +0 -4
  114. package/dist/types/src/theme/components/splitter.d.ts.map +0 -1
  115. package/dist/types/src/theme/components/status.d.ts +0 -9
  116. package/dist/types/src/theme/components/status.d.ts.map +0 -1
  117. package/dist/types/src/theme/components/tag.d.ts +0 -7
  118. package/dist/types/src/theme/components/tag.d.ts.map +0 -1
  119. package/dist/types/src/theme/components/toast.d.ts +0 -12
  120. package/dist/types/src/theme/components/toast.d.ts.map +0 -1
  121. package/dist/types/src/theme/components/toolbar.d.ts +0 -11
  122. package/dist/types/src/theme/components/toolbar.d.ts.map +0 -1
  123. package/dist/types/src/theme/components/tooltip.d.ts +0 -8
  124. package/dist/types/src/theme/components/tooltip.d.ts.map +0 -1
  125. package/dist/types/src/theme/components/treegrid.d.ts +0 -10
  126. package/dist/types/src/theme/components/treegrid.d.ts.map +0 -1
  127. package/dist/types/src/theme/index.d.ts +0 -4
  128. package/dist/types/src/theme/index.d.ts.map +0 -1
  129. package/dist/types/src/theme/primitives/column.d.ts +0 -29
  130. package/dist/types/src/theme/primitives/column.d.ts.map +0 -1
  131. package/dist/types/src/theme/primitives/index.d.ts +0 -3
  132. package/dist/types/src/theme/primitives/index.d.ts.map +0 -1
  133. package/dist/types/src/theme/primitives/panel.d.ts +0 -13
  134. package/dist/types/src/theme/primitives/panel.d.ts.map +0 -1
  135. package/dist/types/src/theme/theme.d.ts +0 -5
  136. package/dist/types/src/theme/theme.d.ts.map +0 -1
  137. package/src/css/components/selected.css +0 -30
  138. package/src/theme/components/avatar.ts +0 -95
  139. package/src/theme/components/breadcrumb.ts +0 -29
  140. package/src/theme/components/button.ts +0 -48
  141. package/src/theme/components/card.ts +0 -102
  142. package/src/theme/components/dialog.ts +0 -61
  143. package/src/theme/components/focus.ts +0 -33
  144. package/src/theme/components/icon-button.ts +0 -18
  145. package/src/theme/components/icon.ts +0 -28
  146. package/src/theme/components/index.ts +0 -30
  147. package/src/theme/components/input.ts +0 -171
  148. package/src/theme/components/link.ts +0 -25
  149. package/src/theme/components/list.ts +0 -46
  150. package/src/theme/components/main.ts +0 -34
  151. package/src/theme/components/menu.ts +0 -50
  152. package/src/theme/components/message.ts +0 -40
  153. package/src/theme/components/popover.ts +0 -41
  154. package/src/theme/components/scroll-area.ts +0 -115
  155. package/src/theme/components/select.ts +0 -52
  156. package/src/theme/components/separator.ts +0 -24
  157. package/src/theme/components/skeleton.ts +0 -23
  158. package/src/theme/components/splitter.ts +0 -20
  159. package/src/theme/components/status.ts +0 -32
  160. package/src/theme/components/tag.ts +0 -23
  161. package/src/theme/components/toast.ts +0 -53
  162. package/src/theme/components/toolbar.ts +0 -35
  163. package/src/theme/components/tooltip.ts +0 -27
  164. package/src/theme/components/treegrid.ts +0 -37
  165. package/src/theme/index.ts +0 -7
  166. package/src/theme/primitives/column.ts +0 -71
  167. package/src/theme/primitives/index.ts +0 -6
  168. package/src/theme/primitives/panel.ts +0 -43
  169. package/src/theme/theme.ts +0 -87
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/ui-theme",
3
- "version": "0.8.4-staging.ac66bdf99f",
3
+ "version": "0.9.0",
4
4
  "description": "A set of design system tokens to use with DXOS UI.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": [
14
14
  "*.css"
@@ -27,13 +27,6 @@
27
27
  "require": "./dist/plugin/node-cjs/plugins/ThemePlugin.cjs"
28
28
  }
29
29
  },
30
- "typesVersions": {
31
- "*": {
32
- "plugin": [
33
- "dist/types/src/plugins/ThemePlugin.d.ts"
34
- ]
35
- }
36
- },
37
30
  "files": [
38
31
  "dist",
39
32
  "src"
@@ -44,29 +37,29 @@
44
37
  "@fontsource/poiret-one": "^5.0.20",
45
38
  "@radix-ui/react-slot": "1.1.2",
46
39
  "@tailwindcss/forms": "^0.5.9",
47
- "@tailwindcss/postcss": "^4.2.0",
48
- "autoprefixer": "^10.4.12",
49
- "esbuild-style-plugin": "^1.6.1",
50
- "glob": "^7.2.3",
40
+ "@tailwindcss/postcss": "^4.3.0",
41
+ "@tailwindcss/vite": "^4.3.0",
42
+ "autoprefixer": "^10.5.0",
43
+ "esbuild-style-plugin": "^1.6.3",
51
44
  "globby": "14.1.0",
52
45
  "lodash.merge": "^4.6.2",
53
- "postcss": "^8.4.41",
46
+ "postcss": "^8.5.12",
54
47
  "postcss-import": "^16.1.0",
55
48
  "postcss-nesting": "^13.0.1",
56
49
  "tailwind-merge": "^3.5.0",
57
50
  "tailwind-scrollbar": "^4.0.0",
58
- "tailwindcss": "^4.2.0",
51
+ "tailwindcss": "^4.3.0",
59
52
  "tailwindcss-radix": "^4.0.2",
60
- "@dxos/log": "0.8.4-staging.ac66bdf99f",
61
- "@dxos/util": "0.8.4-staging.ac66bdf99f",
62
- "@dxos/node-std": "0.8.4-staging.ac66bdf99f"
53
+ "@dxos/log": "0.9.0",
54
+ "@dxos/util": "0.9.0",
55
+ "@dxos/node-std": "0.9.0"
63
56
  },
64
57
  "devDependencies": {
65
58
  "@types/lodash.merge": "^4.6.6",
66
59
  "@types/postcss-import": "^14.0.3",
67
- "esbuild": "0.25.11",
68
- "vite": "^7.1.11",
69
- "@dxos/ui-types": "0.8.4-staging.ac66bdf99f"
60
+ "esbuild": "0.28.0",
61
+ "vite": "^8.0.16",
62
+ "@dxos/ui-types": "0.9.0"
70
63
  },
71
64
  "peerDependencies": {
72
65
  "react": "~19.2.3"
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { type Meta } from '@storybook/react-vite';
6
- import React from 'react';
6
+ import React, { useLayoutEffect, useRef, useState } from 'react';
7
7
 
8
8
  import { hueShades, hues } from './defs';
9
9
  import { mx } from './util';
@@ -13,6 +13,8 @@ const neutralShades: [number, string][] = [
13
13
  [50, 'bg-neutral-50'],
14
14
  [75, 'bg-neutral-75'],
15
15
  [100, 'bg-neutral-100'],
16
+ [125, 'bg-neutral-125'],
17
+ [150, 'bg-neutral-150'],
16
18
  [200, 'bg-neutral-200'],
17
19
  [250, 'bg-neutral-250'],
18
20
  [300, 'bg-neutral-300'],
@@ -22,12 +24,15 @@ const neutralShades: [number, string][] = [
22
24
  [700, 'bg-neutral-700'],
23
25
  [750, 'bg-neutral-750'],
24
26
  [800, 'bg-neutral-800'],
27
+ [825, 'bg-neutral-825'],
28
+ [850, 'bg-neutral-850'],
29
+ [875, 'bg-neutral-875'],
25
30
  [900, 'bg-neutral-900'],
26
31
  [925, 'bg-neutral-925'],
27
32
  [950, 'bg-neutral-950'],
28
33
  ];
29
34
 
30
- const ColorSwatch = ({ hue }: { hue: string }) => {
35
+ const StyleSwatch = ({ hue }: { hue: string }) => {
31
36
  return (
32
37
  <div
33
38
  style={{
@@ -37,17 +42,18 @@ const ColorSwatch = ({ hue }: { hue: string }) => {
37
42
  >
38
43
  <div
39
44
  style={{
40
- color: `var(--color-${hue}-text)`,
41
45
  backgroundColor: `var(--color-base-surface)`,
46
+ color: `var(--color-${hue}-text)`,
42
47
  }}
43
48
  className='p-2 text-sm'
44
49
  >
45
- {hue}
50
+ <div>{hue}</div>
51
+ <div className='text-xs'>-text</div>
46
52
  </div>
47
53
  <div
48
54
  style={{
55
+ backgroundColor: `var(--color-${hue}-bg)`,
49
56
  color: `var(--color-${hue}-text)`,
50
- backgroundColor: `var(--color-${hue}-fill)`,
51
57
  }}
52
58
  className='px-1 text-sm flex items-center'
53
59
  >
@@ -57,12 +63,13 @@ const ColorSwatch = ({ hue }: { hue: string }) => {
57
63
  </div>
58
64
  <div
59
65
  style={{
60
- color: `var(--color-${hue}-surface-text)`,
61
66
  backgroundColor: `var(--color-${hue}-surface)`,
67
+ color: `var(--color-${hue}-fg)`,
62
68
  }}
63
69
  className='p-2 text-sm'
64
70
  >
65
- {hue}
71
+ <div>{hue}</div>
72
+ <div className='text-xs'>-fg/-surface</div>
66
73
  </div>
67
74
  </div>
68
75
  );
@@ -103,7 +110,7 @@ export const Styles = {
103
110
  <div className='p-4'>
104
111
  <div className='flex flex-wrap gap-2'>
105
112
  {['neutral', ...hues].map((hue) => (
106
- <ColorSwatch key={hue} hue={hue} />
113
+ <StyleSwatch key={hue} hue={hue} />
107
114
  ))}
108
115
  </div>
109
116
  </div>
@@ -148,27 +155,87 @@ export const Neutral = {
148
155
  },
149
156
  };
150
157
 
158
+ // prettier-ignore
159
+ const surfaces: [surface: string, foreground: string, label: string][] = [
160
+ // Sorted lightest -> darkest at runtime (see Surfaces story); surfaces without a dedicated
161
+ // foreground fall back to base-fg.
162
+ ['bg-base-surface', 'text-base-fg', 'base'],
163
+ ['bg-deck-surface', 'text-base-fg', 'deck'],
164
+ ['bg-card-surface', 'text-base-fg', 'card'],
165
+ ['bg-toolbar-surface', 'text-base-fg', 'toolbar'],
166
+ ['bg-sidebar-surface', 'text-base-fg', 'sidebar'],
167
+ ['bg-group-surface', 'text-base-fg', 'group'],
168
+ ['bg-header-surface', 'text-base-fg', 'header'],
169
+ ['bg-modal-surface', 'text-base-fg', 'modal'],
170
+ ['bg-l1-surface', 'text-base-fg', 'l1'],
171
+ ['bg-r1-surface', 'text-base-fg', 'r1'],
172
+ ['bg-hover-surface', 'text-hover-fg', 'hover'],
173
+ ['bg-current-surface', 'text-current-fg', 'current'],
174
+ ['bg-selected-surface','text-selected-fg', 'selected'],
175
+ ['bg-l0-surface', 'text-base-fg', 'l0'],
176
+ ['bg-r0-surface', 'text-base-fg', 'r0'],
177
+ ['bg-input-surface', 'text-input-fg', 'input'],
178
+ ['bg-inverse-surface', 'text-inverse-fg', 'inverse'],
179
+ ['bg-scrim-surface', 'text-base-fg', 'scrim'],
180
+ ];
181
+
182
+ // Resolve any CSS color (oklch, light-dark(), rgb, ...) to a 0-1 luminance via a 1x1 canvas.
183
+ const colorLuminance = (css: string): number => {
184
+ const canvas = document.createElement('canvas');
185
+ canvas.width = canvas.height = 1;
186
+ const ctx = canvas.getContext('2d');
187
+ if (!ctx) {
188
+ return 0;
189
+ }
190
+ ctx.clearRect(0, 0, 1, 1);
191
+ ctx.fillStyle = css;
192
+ ctx.fillRect(0, 0, 1, 1);
193
+ const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
194
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
195
+ };
196
+
151
197
  export const Surfaces = {
152
198
  render: () => {
199
+ const rowRefs = useRef(new Map<string, HTMLDivElement>());
200
+ const [order, setOrder] = useState(surfaces);
201
+ useLayoutEffect(() => {
202
+ // Sort lightest -> darkest by the resolved background; re-runs when the theme toggles.
203
+ const sort = () => {
204
+ const ranked = surfaces
205
+ .map((row) => {
206
+ const element = rowRefs.current.get(row[2]);
207
+ return { row, luminance: element ? colorLuminance(getComputedStyle(element).backgroundColor) : 0 };
208
+ })
209
+ .sort((a, b) => b.luminance - a.luminance)
210
+ .map(({ row }) => row);
211
+ setOrder(ranked);
212
+ };
213
+ sort();
214
+ const observer = new MutationObserver(sort);
215
+ observer.observe(document.body, {
216
+ attributes: true,
217
+ subtree: true,
218
+ attributeFilter: ['class', 'style', 'data-theme'],
219
+ });
220
+ return () => observer.disconnect();
221
+ }, []);
153
222
  return (
154
- <div className='absolute inset-0 h-full p-4 bg-white dark:bg-black'>
155
- <div className='flex flex-wrap gap-2'>
156
- {[
157
- { className: 'bg-scrim-surface', label: 'scrim' },
158
- { className: 'bg-base-surface', label: 'base' },
159
- { className: 'bg-deck-surface', label: 'deck' },
160
- { className: 'bg-group-surface', label: 'group' },
161
- { className: 'bg-sidebar-surface', label: 'sidebar' },
162
- { className: 'bg-header-surface', label: 'header' },
163
- { className: 'bg-card-surface', label: 'card' },
164
- { className: 'bg-modal-surface', label: 'modal' },
165
- { className: 'bg-input-surface', label: 'input' },
166
- { className: 'bg-active-surface', label: 'active' },
167
- { className: 'bg-hover-surface', label: 'hover' },
168
- { className: 'bg-inverse-surface text-inverse-surface-text', label: 'inverse' },
169
- ].map(({ className, label }) => (
170
- <div key={className} className={mx('shrink-0 p-2 aspect-square w-48 rounded-md', className)}>
171
- {label}
223
+ <div className='absolute inset-0 overflow-auto bg-white dark:bg-black'>
224
+ <div className='flex flex-col'>
225
+ {order.map(([surface, foreground, label]) => (
226
+ <div
227
+ key={label}
228
+ ref={(element) => {
229
+ if (element) {
230
+ rowRefs.current.set(label, element);
231
+ }
232
+ }}
233
+ className={mx('flex items-baseline justify-between px-4 py-3', surface, foreground)}
234
+ >
235
+ <span className='text-sm'>{label}</span>
236
+ <span className='text-xs opacity-60'>
237
+ {surface} · {foreground}
238
+ </span>
172
239
  </div>
173
240
  ))}
174
241
  </div>
@@ -206,7 +273,7 @@ export const Animation = {
206
273
  render: () => {
207
274
  return (
208
275
  <div className='absolute inset-0 grid place-items-center'>
209
- <div className='dx-density-coarse border border-separator rounded-md'>
276
+ <div className='dx-density-lg border border-separator rounded-md'>
210
277
  <div
211
278
  className={mx(
212
279
  'flex items-center font-mono text-2xl text-test-experimental',
@@ -0,0 +1,187 @@
1
+ # Design System: Color Tokens
2
+
3
+ This document describes how color tokens are organized in `ui-theme`, the naming rules they follow, and the rationalization of overlapping state tokens (`current` / `active` / `highlight` / `selected` / `hover`) and text-on-surface tokens (`surface-text` → `foreground`).
4
+
5
+ ## Token tiers
6
+
7
+ Three layers, each consuming the one below:
8
+
9
+ | Tier | File | Purpose | Example |
10
+ | ----------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
11
+ | 1. Scale | [`theme/palette.css`](./theme/palette.css) | Raw color values. Extends Tailwind's neutral/blue scales with intermediate stops and aliases the `primary-*` ramp to `blue-*`. | `--color-neutral-150`, `--color-primary-500` |
12
+ | 2. Hue role | [`theme/styles.css`](./theme/styles.css) | Per-hue role tokens for every Tailwind hue plus `neutral`. Six roles each: `bg`, `bg-hover`, `surface`, `fg`, `text`, `border`. Light/dark resolved via `light-dark()`. | `--color-red-surface`, `--color-neutral-border` |
13
+ | 3. Semantic | [`theme/semantic.css`](./theme/semantic.css) | Named UI surfaces and states. May reference hue-role tokens (e.g. `error-surface` → `rose-surface`) or compose directly from the scale. | `--color-card-surface`, `--color-current-surface`, `--color-error-text` |
14
+
15
+ A consumer should reach for the highest tier that fits. Use `bg-card-surface`, not `bg-neutral-825`. Use `text-error-text`, not `text-rose-700`.
16
+
17
+ ## Naming convention
18
+
19
+ Token names follow `--color-{name}-{part}[-{state}]`:
20
+
21
+ - **`name`** identifies the role or surface (`card`, `current`, `accent`, `error`, `red`).
22
+ - **`part`** is one of a fixed vocabulary:
23
+ - `surface` — the background fill of a thing.
24
+ - `fg` — the text/icon color that sits on the surface (paired with `surface`).
25
+ - `bg` — solid attention-grabbing fill (buttons, badges, indicators); more saturated than `surface`.
26
+ - `bg-hover` — hover state for `bg`.
27
+ - `border` — border color on the surface.
28
+ - `text` — text color used standalone, on the base canvas (no enclosing surface).
29
+ - **`state`** is optional, appended last: `hover`, `active` (pressed). State always follows the part it modifies.
30
+
31
+ ### Pattern rules
32
+
33
+ - Suffix order is fixed: `{name}-{part}-{state}`. The part comes first, then the state.
34
+ - Pair rule: every `{name}-surface` that hosts text has a matching `{name}-fg`. Don't invent new ink-suffix names — keep the vocabulary closed.
35
+ - A state token implies a base — `current-surface-hover` only makes sense if `current-surface` exists.
36
+ - The `-text` part (standalone) is reserved for text on the base canvas, with no enclosing surface (e.g. `accent-text` for a hyperlink in body copy). Text on any named surface uses `{name}-fg`.
37
+
38
+ ## Surfaces
39
+
40
+ Surfaces are governed by a strict elevation ladder: **lighter = higher = closer to the viewer** in dark
41
+ mode; inverted toward white in light mode. Every named surface token is an alias of exactly one
42
+ `--dx-elevation-N` level. Never set a surface to a raw scale value — pick the level that matches the
43
+ role and point the token there.
44
+
45
+ | Level | Name | Dark | Light | Named surfaces |
46
+ | ----- | -------- | ------- | ------- | ----------------------------------------------------------------------------- |
47
+ | 0 | `void` | `n-950` | `n-200` | scrim base, window gaps |
48
+ | 1 | `rail` | `n-900` | `n-175` | `l0-surface` (icon rail) |
49
+ | 2 | `chrome` | `n-875` | `n-150` | `sidebar-surface`, `header-surface`, `l1-surface`, `r0-surface`, `r1-surface` |
50
+ | 3 | `canvas` | `n-850` | `n-125` | `base-surface`, `deck-surface` |
51
+ | 4 | `raised` | `n-825` | `n-100` | `card-surface`, `group-surface`, `input-surface` |
52
+ | 5 | `bar` | `n-800` | `n-75` | `toolbar-surface` (sticky, drop-shadowed) |
53
+ | 6 | `modal` | `n-775` | `n-50` | `modal-surface` (dialogs) |
54
+ | 7 | `float` | `n-750` | `white` | `popover-surface` (menus, popovers, toasts, tooltips) |
55
+
56
+ The primitive `--dx-elevation-0…7` is defined in `semantic.css` using `light-dark()`. Raw scale values
57
+ (`n-*`) are in `palette.css` — the table above is for human reference only.
58
+
59
+ ### Visual hierarchy (dark)
60
+
61
+ ```text
62
+ popover/float n-750 ↑ highest / closest to viewer
63
+ modal/dialog n-775
64
+ toolbar n-800 (sticky bar; content passes beneath)
65
+ card/raised n-825
66
+ canvas/deck n-850
67
+ chrome/sidebar n-875
68
+ rail/L0 n-900
69
+ void n-950 ↓ lowest
70
+ ```
71
+
72
+ Each surface that hosts text declares a matching `*-fg` (defaulting to `n-950 / n-50`).
73
+
74
+ ## Elevation primitive
75
+
76
+ The `--dx-elevation-0…7` custom properties in `semantic.css` are the single source of truth for the
77
+ surface ladder. They are private (`--dx-*` prefix) — never use them directly in component CSS; use the
78
+ named surface tokens (`bg-card-surface`, `dx-modal-surface`, etc.) instead.
79
+
80
+ When adding a new surface:
81
+
82
+ 1. Decide which elevation level the new surface belongs to (see the table above).
83
+ 2. Add `--color-<name>-surface: var(--dx-elevation-N);` in the Surfaces block of `semantic.css`.
84
+ 3. Add a matching `--color-<name>-fg` if text/icons sit on it.
85
+ 4. If the surface needs a utility class (like `dx-modal-surface`), add it to `surface.css`.
86
+
87
+ ## State tokens (rationalized)
88
+
89
+ The system has three orthogonal states. Pick by what the ARIA / markup is saying.
90
+
91
+ | State | Token | When | Value |
92
+ | -------------------------- | ------------------------ | ---------------------------------------------------------------- | ----------------- |
93
+ | Active item, one-of-N | `current-surface` | `aria-current=true` (nav cursor, current row, current path) | `n-100` / `n-900` |
94
+ | Hovering on current item | `current-surface-hover` | pointer-over on a `current` element | `n-200` / `n-800` |
95
+ | Text on a current surface | `current-fg` | text/icon color paired with `current-surface` | `n-950` / `n-50` |
96
+ | Selected / checked | `selected-surface` | `aria-selected=true` (multi-select, listbox option, checked row) | `n-150` / `n-850` |
97
+ | Hovering on selected | `selected-surface-hover` | pointer-over on a `selected` element | `n-250` / `n-750` |
98
+ | Text on a selected surface | `selected-fg` | text/icon color paired with `selected-surface` | `n-950` / `n-50` |
99
+ | Transient pointer-over | `hover-surface` | `:hover`, Radix `data-highlighted` (keyboard cursor in menus) | `n-250` / `n-750` |
100
+ | Text on a transient hover | `hover-fg` | text/icon color paired with `hover-surface` | `n-950` / `n-50` |
101
+
102
+ **Why these three.** `current` describes one-of-N navigation/selection state ("you are here"); `selected` describes a checked item in a set (multi-select-able); `hover` is transient pointer feedback. Driving the distinction off ARIA keeps markup and tokens in sync.
103
+
104
+ ### Visual hierarchy (state, dark)
105
+
106
+ ```text
107
+ card-surface n-825 resting
108
+ current-surface n-100 / n-900 "I am the active one"
109
+ current-surface-hover n-200 / n-800 hovering the active one
110
+ hover-surface n-250 / n-750 transient pointer-over anywhere else
111
+ ```
112
+
113
+ ## Consolidation: tokens that go away
114
+
115
+ These are merged into the rationalized state vocabulary:
116
+
117
+ | Removed | Replaced by | Notes |
118
+ | ------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | `active-surface` | `current-surface` | Same concept ("the active one"). Same value already. |
120
+ | `active-text` | `current-fg` | Renamed and reclassified — it was always foreground (text on the surface), not `text`. |
121
+ | `highlight-surface` | `current-surface` | `selected.css` used it for `aria-current=true`. |
122
+ | `highlight-surface-hover` | `current-surface-hover` | |
123
+ | `highlight-surface-text` | `current-fg` | Rename to the new `-fg` suffix. |
124
+ | `*-surface-text` (all) | `*-fg` | Repository-wide rename for every hue, semantic, and named-surface token (e.g. `base-surface-text` → `base-fg`, `red-surface-text` → `red-fg`, `error-surface-text` → `error-fg`). |
125
+
126
+ ### Out-of-scope drift to fix separately
127
+
128
+ Two sites use `bg-hover-surface` as a _resting_ fill (not a hover state):
129
+
130
+ - [packages/plugins/plugin-navtree/src/experimental/Tree.tsx:196](../../../../plugins/plugin-navtree/src/experimental/Tree.tsx)
131
+ - [packages/plugins/plugin-script/src/components/TestPanel/TestPanel.tsx:171](../../../../plugins/plugin-script/src/components/TestPanel/TestPanel.tsx)
132
+
133
+ These should probably move to `card-surface` or `current-surface` depending on intent. Tracked as a follow-up.
134
+
135
+ ## Accent (primary)
136
+
137
+ `accent-*` is the project's primary brand color (blue). The token name is `accent`, not `primary`, to avoid colliding with the raw scale `--color-primary-*` (which is aliased to `blue-*` for Tailwind utility convenience).
138
+
139
+ | Token | Purpose |
140
+ | ------------------- | --------------------------------------------------- |
141
+ | `accent-bg` | Fill for primary buttons / call-to-action surfaces. |
142
+ | `accent-bg-hover` | Hover state. |
143
+ | `accent-fg` | Text/icon color on accent surfaces. |
144
+ | `accent-text` | Standalone accent-colored text (links, emphasis). |
145
+ | `accent-text-hover` | Hover state for accent text. |
146
+
147
+ This is the canonical illustration of the `fg` vs `text` distinction: `accent-fg` is the ink on an accent button; `accent-text` is the accent-colored body link.
148
+
149
+ Note: `accent-focus-indicator` is removed — focus rings are now global tokens (see [Focus rings](#focus-rings)).
150
+
151
+ ## Focus rings
152
+
153
+ Focus indicators are a global UI affordance, not a per-surface property. Every focusable element should pick from the same small set of ring colors so focus stays visually consistent and accessible.
154
+
155
+ | Token | Value | Used by |
156
+ | ------------------- | ----------------- | -------------------------------------------------------- |
157
+ | `focus-ring` | `blue-600` | Default focus ring (buttons, inputs, grid cells, links). |
158
+ | `focus-ring-subtle` | `n-400` / `n-600` | Subdued ring for low-emphasis focusable elements. |
159
+
160
+ Tokens removed in this rationalization:
161
+
162
+ | Removed | Replaced by | Notes |
163
+ | ------------------------- | ------------------- | -------------------------------------------------------------- |
164
+ | `accent-focus-indicator` | `focus-ring` | Was `blue-600`. Same value. |
165
+ | `grid-focus-indicator` | `focus-ring` | Was `primary-600` (= `blue-600`). Identical to accent variant. |
166
+ | `neutral-focus-indicator` | `focus-ring-subtle` | Was `n-400` / `n-600`. Same value. |
167
+
168
+ ## Semantic aliases
169
+
170
+ Status colors point at hue-role tokens:
171
+
172
+ | Token | Aliases |
173
+ | ----------- | ----------- |
174
+ | `info-*` | `cyan-*` |
175
+ | `success-*` | `emerald-*` |
176
+ | `warning-*` | `amber-*` |
177
+ | `error-*` | `rose-*` |
178
+
179
+ Each provides `bg`, `bg-hover`, `surface`, `fg`, `text`, `border`.
180
+
181
+ ## Adding a new token
182
+
183
+ 1. Does an existing semantic token already cover it? For a new named surface, check the elevation ladder first — the new surface probably fits an existing level and should alias `--dx-elevation-N` rather than a raw scale value.
184
+ 2. Does it represent a new named surface, state, or status? Add it to `semantic.css` referencing scale or hue-role tokens — never raw hex.
185
+ 3. Follow the suffix order: `{name}-{part}[-{state}]`.
186
+ 4. If the new token will be used through a Tailwind utility that the source-scan can't see (e.g. CSS file, dynamic class), add it to `@source inline(...)` in [`main.css`](./main.css).
187
+ 5. If it ships state variants (`-hover`, `-active`), declare them adjacent to the base in the same block.
@@ -35,9 +35,9 @@
35
35
  html .dark,
36
36
  .sb-show-main {
37
37
  background-color: var(--color-base-surface);
38
- color: var(--color-base-surface-text);
38
+ color: var(--color-base-fg);
39
39
  --surface-bg: var(--color-base-surface);
40
- --surface-text: var(--color-base-surface-text);
40
+ --foreground: var(--color-base-fg);
41
41
  --description-text: var(--color-description);
42
42
  }
43
43
  }
@@ -10,8 +10,14 @@
10
10
 
11
11
  /* Base styles */
12
12
  .dx-button {
13
- @apply shrink-0 inline-flex select-none items-center justify-center overflow-hidden min-h-[2.5rem] px-3;
14
- @apply font-medium transition-colors duration-100 ease-linear bg-input-surface;
13
+ @apply shrink-0 inline-flex select-none items-center justify-center overflow-hidden min-h-[2rem] px-3;
14
+ @apply font-medium transition-colors duration-100 ease-linear bg-input-bg;
15
+ /* The default variant inherits ambient valence colors when rendered inside a valence surface
16
+ * (e.g. Message.Root, which sets --dx-valence-*); otherwise it falls back to the base input styling. */
17
+ &[data-variant='default'] {
18
+ color: var(--dx-valence-text, inherit);
19
+ background: var(--dx-valence-bg, var(--color-input-bg));
20
+ }
15
21
  &[aria-pressed='true'],
16
22
  &[aria-checked='true'] {
17
23
  @apply text-accent-text bg-attention-surface;
@@ -26,10 +32,17 @@
26
32
  &[data-variant='ghost'] {
27
33
  @apply bg-transparent;
28
34
  &:hover {
29
- @apply bg-hover-surface text-inherit;
35
+ /* Tint relative to the host surface (--surface-bg, set by the enclosing dx-*-surface zone)
36
+ * so the hover reads correctly on any background: darker in light mode, lighter in dark.
37
+ * Falls back to base-surface when no surface zone is in scope. */
38
+ background: light-dark(
39
+ oklch(from var(--surface-bg, var(--color-base-surface)) calc(l - 0.05) c h),
40
+ oklch(from var(--surface-bg, var(--color-base-surface)) calc(l + 0.05) c h)
41
+ );
42
+ @apply text-inherit;
30
43
  }
31
44
  &[data-state='open'] {
32
- @apply bg-input-surface;
45
+ @apply bg-input-bg;
33
46
  }
34
47
  &[aria-pressed='true'],
35
48
  &[aria-checked='true'] {
@@ -46,30 +59,55 @@
46
59
  /* Enabled styles */
47
60
  &:not([disabled]),
48
61
  &[disabled='false'] {
49
- /* Hover */
50
62
  &:hover {
51
63
  @apply bg-hover-surface;
52
64
  }
53
65
 
54
- /* Variants */
66
+ &[data-variant='default']:hover {
67
+ color: var(--dx-valence-text);
68
+ background-color: var(--dx-valence-bg-hover, var(--color-hover-surface));
69
+ }
70
+
71
+ /*
72
+ * Inside a valence surface the (otherwise transparent) ghost hover adopts the valence background;
73
+ * elsewhere it falls back to the standard hover surface.
74
+ */
75
+ &[data-variant='ghost']:hover {
76
+ color: var(--dx-valence-text);
77
+ background-color: var(--dx-valence-bg, var(--color-hover-surface));
78
+ }
79
+
55
80
  &[data-variant='primary'] {
56
- @apply text-accent-surface-text bg-accent-surface;
81
+ @apply text-accent-fg bg-accent-bg;
57
82
  &:hover,
58
83
  &[aria-pressed='true'],
59
- &[aria-checked='true'] &[data-state='open'] {
60
- @apply bg-accent-surface-hover;
84
+ &[aria-checked='true'],
85
+ &[data-state='open'] {
86
+ @apply bg-accent-bg-hover;
61
87
  }
62
88
  }
63
89
  &[data-variant='destructive'] {
64
- @apply text-accent-surface-text bg-rose-fill;
90
+ @apply text-accent-fg bg-error-bg;
91
+ &:hover,
92
+ &[aria-pressed='true'],
93
+ &[aria-checked='true'],
94
+ &[data-state='open'] {
95
+ @apply bg-error-bg-hover;
96
+ }
97
+ }
98
+ &[data-variant='valence'] {
99
+ @apply text-inverse-fg;
100
+ background: var(--dx-valence-bg, var(--color-neutral-bg));
65
101
  &:hover,
66
102
  &[aria-pressed='true'],
67
- &[aria-checked='true'] &[data-state='open'] {
68
- @apply bg-rose-fill;
103
+ &[aria-checked='true'],
104
+ &[data-state='open'] {
105
+ background-color: var(--dx-valence-bg-hover, var(--color-neutral-bg-hover));
69
106
  }
70
107
  }
71
108
  }
72
109
  }
110
+
73
111
  /* Props */
74
112
  .dx-button:not([data-props~='grouped']) {
75
113
  @apply rounded-xs;
@@ -77,8 +115,20 @@
77
115
  .dx-button:not([data-props~='wrap']) {
78
116
  @apply truncate;
79
117
  }
118
+ .dx-button[data-density='lg'] {
119
+ @apply min-h-[2.5rem] px-3;
120
+ }
121
+ .dx-button[data-density='sm'] {
122
+ @apply min-h-[1.75rem] px-2;
123
+ }
124
+ .dx-button[data-density='xs'] {
125
+ @apply min-h-[1.5rem] px-1 text-xs;
126
+ }
127
+ .dx-button[data-density='xs'].aspect-square {
128
+ @apply size-6 px-0;
129
+ }
80
130
  @media (pointer: fine) {
81
- .dx-button[data-density='fine'] {
131
+ .dx-button[data-density='md'] {
82
132
  @apply min-h-[2rem] px-2.5;
83
133
  }
84
134
  }
@@ -0,0 +1,14 @@
1
+ @layer dx-components {
2
+ /**
3
+ * Card.Section is `col-span-full grid grid-cols-subgrid` so it owns a full row and
4
+ * exposes the Card.Root's 3 column tracks (gutter / content / gutter) to its children.
5
+ *
6
+ * Direct children that do not carry their own column placement default to the center
7
+ * (content) track via `--dx-col`, so raw content (editors, text, lists) lands in the
8
+ * content column instead of CSS Grid auto-placing it into a gutter. Children with
9
+ * an explicit `col-span-*` utility (Card.Action, Card.Row, Card.Section title) opt out.
10
+ */
11
+ .dx-card__section > *:not([class*='col-span']) {
12
+ grid-column: var(--dx-col, auto);
13
+ }
14
+ }
@@ -10,7 +10,7 @@
10
10
  &[aria-checked='true'],
11
11
  &[aria-checked='mixed'],
12
12
  &:checked {
13
- @apply bg-accent-surface accent-accent-surface border-accent-surface;
13
+ @apply bg-accent-bg accent-accent-bg border-accent-bg;
14
14
  }
15
15
 
16
16
  &:not([disabled]),
@@ -20,14 +20,14 @@
20
20
  &[aria-checked='true'],
21
21
  &[aria-checked='mixed'],
22
22
  &:checked {
23
- @apply bg-accent-surface-hover accent-accent-surface-hover border-accent-surface-hover;
23
+ @apply bg-accent-bg-hover accent-accent-bg-hover border-accent-bg-hover;
24
24
  }
25
25
  }
26
26
  }
27
27
  }
28
28
 
29
29
  .dx-checkbox {
30
- @apply w-4 h-4 overflow-hidden shadow-inner transition-colors bg-un-accent accent-un-accent text-accent-surface-text shrink-0 inline-grid place-items-center rounded-xs;
30
+ @apply w-4 h-4 overflow-hidden shadow-inner transition-colors bg-un-accent accent-un-accent text-accent-fg shrink-0 inline-grid place-items-center rounded-xs;
31
31
  }
32
32
 
33
33
  /* TODO(ZaymonFC): Focus is handled by .dx-focus-ring, but should ideally be applied as part of this component.*/