@dxos/ui-theme 0.10.0 → 0.11.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 (57) hide show
  1. package/dist/lib/browser/index.mjs +0 -62
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +0 -62
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/plugin/node-cjs/main.css +311 -189
  8. package/dist/plugin/node-cjs/main.css.map +3 -3
  9. package/dist/plugin/node-cjs/meta.json +1 -1
  10. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs +2 -1
  11. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs.map +3 -3
  12. package/dist/plugin/node-esm/main.css +311 -189
  13. package/dist/plugin/node-esm/main.css.map +3 -3
  14. package/dist/plugin/node-esm/meta.json +1 -1
  15. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs +2 -1
  16. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs.map +3 -3
  17. package/dist/types/src/Theme.stories.d.ts.map +1 -1
  18. package/dist/types/src/defs.d.ts +0 -5
  19. package/dist/types/src/defs.d.ts.map +1 -1
  20. package/dist/types/src/fragments/hover.d.ts +0 -2
  21. package/dist/types/src/fragments/hover.d.ts.map +1 -1
  22. package/dist/types/src/fragments/index.d.ts +0 -1
  23. package/dist/types/src/fragments/index.d.ts.map +1 -1
  24. package/dist/types/src/plugins/ThemePlugin.d.ts.map +1 -1
  25. package/dist/types/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +11 -6
  27. package/src/Theme.stories.tsx +3 -1
  28. package/src/css/DESIGN_SYSTEM.md +89 -51
  29. package/src/css/base/base.css +39 -0
  30. package/src/css/components/button.css +21 -16
  31. package/src/css/components/callout.css +140 -0
  32. package/src/css/components/input.css +36 -0
  33. package/src/css/components/state.css +1 -11
  34. package/src/css/components/state.md +1 -1
  35. package/src/css/components/surface.css +165 -58
  36. package/src/css/components/tag.css +13 -1
  37. package/src/css/integrations/codemirror.css +15 -0
  38. package/src/css/layout/main.css +26 -2
  39. package/src/css/layout/native.css +1 -0
  40. package/src/css/theme/roles.css +92 -0
  41. package/src/css/theme/spacing.css +63 -54
  42. package/src/css/theme/surfaces.css +215 -0
  43. package/src/css/theme/text.css +6 -2
  44. package/src/css/utilities.css +13 -9
  45. package/src/defs.ts +0 -25
  46. package/src/fragments/hover.ts +0 -3
  47. package/src/fragments/index.ts +0 -1
  48. package/src/main.css +8 -10
  49. package/src/plugins/ThemePlugin.ts +50 -40
  50. package/src/plugins/main.css +1 -1
  51. package/src/tokens.css +22 -0
  52. package/dist/types/src/fragments/density.d.ts +0 -4
  53. package/dist/types/src/fragments/density.d.ts.map +0 -1
  54. package/src/css/components/panel.css +0 -117
  55. package/src/css/theme/semantic.css +0 -244
  56. package/src/fragments/AUDIT.md +0 -55
  57. package/src/fragments/density.ts +0 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/ui-theme",
3
- "version": "0.10.0",
3
+ "version": "0.11.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",
@@ -20,6 +20,10 @@
20
20
  "types": "./dist/types/src/index.d.ts",
21
21
  "default": "./dist/lib/browser/index.mjs"
22
22
  },
23
+ "./tokens.css": {
24
+ "style": "./src/tokens.css",
25
+ "default": "./src/tokens.css"
26
+ },
23
27
  "./plugin": {
24
28
  "source": "./src/plugins/ThemePlugin.ts",
25
29
  "types": "./dist/types/src/plugins/ThemePlugin.d.ts",
@@ -34,6 +38,7 @@
34
38
  "dependencies": {
35
39
  "@fontsource-variable/inter": "5.2.8",
36
40
  "@fontsource-variable/jetbrains-mono": "5.0.21",
41
+ "@fontsource-variable/nunito": "5.2.7",
37
42
  "@fontsource/poiret-one": "^5.0.20",
38
43
  "@radix-ui/react-slot": "1.1.2",
39
44
  "@tailwindcss/forms": "^0.5.9",
@@ -51,16 +56,16 @@
51
56
  "tailwind-variants": "^3.2.2",
52
57
  "tailwindcss": "^4.3.0",
53
58
  "tailwindcss-radix": "^4.0.2",
54
- "@dxos/log": "0.10.0",
55
- "@dxos/node-std": "0.10.0",
56
- "@dxos/util": "0.10.0"
59
+ "@dxos/log": "0.11.0",
60
+ "@dxos/node-std": "0.11.0",
61
+ "@dxos/util": "0.11.0"
57
62
  },
58
63
  "devDependencies": {
59
64
  "@types/lodash.merge": "^4.6.6",
60
65
  "@types/postcss-import": "^14.0.3",
61
66
  "esbuild": "0.28.1",
62
- "vite": "^8.0.16",
63
- "@dxos/ui-types": "0.10.0"
67
+ "vite": "^8.1.4",
68
+ "@dxos/ui-types": "0.11.0"
64
69
  },
65
70
  "peerDependencies": {
66
71
  "react": "~19.2.7"
@@ -5,7 +5,9 @@
5
5
  import { type Meta } from '@storybook/react-vite';
6
6
  import React, { useLayoutEffect, useRef, useState } from 'react';
7
7
 
8
- import { hues, hueShades } from './defs';
8
+ import { hues } from '@dxos/ui-types';
9
+
10
+ import { hueShades } from './defs';
9
11
  import { mx } from './util';
10
12
 
11
13
  // prettier-ignore
@@ -6,11 +6,11 @@ This document describes how color tokens are organized in `ui-theme`, the naming
6
6
 
7
7
  Three layers, each consuming the one below:
8
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` |
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/surfaces.css`](./theme/surfaces.css) + [`theme/roles.css`](./theme/roles.css) | Named UI surfaces/aspects, and the non-surface roles. 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
14
 
15
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
16
 
@@ -38,76 +38,114 @@ Token names follow `--color-{name}-{part}[-{state}]`:
38
38
  ## Surfaces
39
39
 
40
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.
41
+ mode; inverted toward white in light mode. Chrome sits **below** the document canvas and content
42
+ surfaces sit **above** it, so the app reads as paper on a desk rather than paper in a well.
43
+
44
+ There are exactly six **levels**. Every named surface token is an alias of exactly one of them —
45
+ never set a surface to a raw scale value. This table mirrors the assignments in `surfaces.css`,
46
+ which is the source of truth; update both together.
47
+
48
+ | Level | Dark | Light | Named surfaces |
49
+ | --------- | ------- | ------- | ---------------------------------------------------------- |
50
+ | `sunken` | `n-950` | `n-300` | `deck-surface`, scrim base |
51
+ | `chrome` | `n-925` | `n-250` | `sidebar-surface`, `header-surface`, `l0/l1/r0/r1-surface` |
52
+ | `base` | `n-900` | `n-200` | `base-surface`, `attention-surface` |
53
+ | `raised` | `n-850` | `n-125` | `card-surface` |
54
+ | `overlay` | `n-800` | `n-75` | `modal-surface` (dialogs, sheets, drawers) |
55
+ | `popup` | `n-775` | `n-50` | `popover-surface` (menus, popovers, toasts, tooltips) |
56
+
57
+ The primitive `--dx-surface-{sunken…popup}` is defined in `surfaces.css` using `light-dark()`. Raw
58
+ scale values (`n-*`) are in `palette.css` — the table above is for human reference only.
59
+
60
+ ### Aspects — derived, not levels
61
+
62
+ Toolbars, groups and inputs are **not** levels. They are _aspects_: a step off whichever surface
63
+ hosts them, computed with relative oklch from `--surface-bg` and stepped by `--dx-lift` (`+1` dark,
64
+ `-1` light, set in `base.css`). That is what makes nesting work — a toolbar in a card and a toolbar
65
+ on the canvas each read as a bar on their own host instead of jumping to one global tone.
66
+
67
+ | Aspect | Offset | Tokens |
68
+ | ------------- | ------- | ------------------------------------------------- |
69
+ | bar | `0.025` | `toolbar-surface` |
70
+ | well | `0.05` | `group-surface`, `input-surface`, `focus-surface` |
71
+ | hover-subtle | `0.02` | `hover-surface-subtle` |
72
+ | hover | `0.08` | `hover-surface`, `group-alt-surface` |
73
+ | current | `0.10` | `current-surface`, `selected-surface` |
74
+ | current-hover | `0.12` | `current-surface-hover`, `selected-surface-hover` |
75
+
76
+ **Entering a zone.** A zone paints the level _and_ publishes `--surface-bg`, from which the aspects
77
+ re-derive for that subtree. Use `data-surface="<level>"` or the equivalent `dx-*-surface` class — a
78
+ bare `bg-*-surface` utility paints the colour but publishes nothing, so every aspect inside it
79
+ collapses onto the base-surface fallback.
80
+
81
+ Two mechanical rules govern the derivation blocks in `components/surface.css`:
82
+
83
+ - **Aliases must be re-declared per zone.** A `--color-x: var(--color-y)` alias substitutes at the
84
+ scope that _declares_ it, so an alias written only at `:root` freezes to the root value.
85
+ - **Bar and well re-derive only in level zones.** An aspect zone defines its own `--surface-bg`
86
+ _from_ those tokens, so re-deriving them there would be circular.
58
87
 
59
88
  ### Visual hierarchy (dark)
60
89
 
61
90
  ```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
91
+ popup n-775 ↑ highest / closest to viewer
92
+ overlay n-800
93
+ raised / card n-850
94
+ base / canvas n-900
95
+ chrome/sidebar n-925
96
+ sunken / deck n-950 ↓ lowest
70
97
  ```
71
98
 
72
- Each surface that hosts text declares a matching `*-fg` (defaulting to `n-950 / n-50`).
99
+ Each surface that hosts text declares a matching `*-fg` (defaulting to `n-950 / n-150`).
73
100
 
74
101
  ## Elevation primitive
75
102
 
76
- The `--dx-elevation-0…7` custom properties in `semantic.css` are the single source of truth for the
103
+ The `--dx-surface-*` custom properties in `surfaces.css` are the single source of truth for the
77
104
  surface ladder. They are private (`--dx-*` prefix) — never use them directly in component CSS; use the
78
105
  named surface tokens (`bg-card-surface`, `dx-modal-surface`, etc.) instead.
79
106
 
80
107
  When adding a new surface:
81
108
 
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`.
109
+ 1. Decide whether it is a **level** or an **aspect**. If it should shift with whatever hosts it
110
+ (a bar, a well, a state), it is an aspect — add it to the aspect block, not the ladder.
111
+ 2. For a level, add `--color-<name>-surface: var(--dx-surface-<level>);` in `surfaces.css`.
84
112
  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`.
113
+ 4. Add the zone to `surface.css` so it publishes `--surface-bg` and re-derives the aspects.
86
114
 
87
115
  ## State tokens (rationalized)
88
116
 
89
117
  The system has three orthogonal states. Pick by what the ARIA / markup is saying.
90
118
 
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` |
119
+ State surfaces are **derived from the host surface**, not fixed scale values: each token is a
120
+ relative-oklch lightness offset from `--surface-bg` (published by the enclosing `dx-*-surface` zone
121
+ class see `components/surface.css`), darker in light mode and lighter in dark mode, so a highlight
122
+ stays visible at any elevation. Offsets (L): hover-subtle ∓0.02, hover ∓0.08, current ∓0.10,
123
+ current-hover ∓0.12; `selected-*` aliases `current-*`.
124
+
125
+ | State | Token | When |
126
+ | -------------------------- | ------------------------ | ---------------------------------------------------------------- |
127
+ | Active item, one-of-N | `current-surface` | `aria-current=true` (nav cursor, current row, current path) |
128
+ | Hovering on current item | `current-surface-hover` | pointer-over on a `current` element |
129
+ | Text on a current surface | `current-fg` | text/icon color paired with `current-surface` |
130
+ | Selected / checked | `selected-surface` | `aria-selected=true` (multi-select, listbox option, checked row) |
131
+ | Hovering on selected | `selected-surface-hover` | pointer-over on a `selected` element |
132
+ | Text on a selected surface | `selected-fg` | text/icon color paired with `selected-surface` |
133
+ | Transient pointer-over | `hover-surface` | `:hover`, Radix `data-highlighted` (keyboard cursor in menus) |
134
+ | Subtle region hover | `hover-surface-subtle` | large targets (rows, tiles) via `dx-hover` |
135
+ | Text on a transient hover | `hover-fg` | text/icon color paired with `hover-surface` |
101
136
 
102
137
  **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
138
 
104
139
  ### Visual hierarchy (state, dark)
105
140
 
141
+ Relative to the host surface `S` (= `--surface-bg`), in oklch lightness:
142
+
106
143
  ```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
144
+ S resting
145
+ S + 0.02 subtle region hover (hover-surface-subtle)
146
+ S + 0.08 transient pointer-over (hover-surface)
147
+ S + 0.10 "I am the active one" (current-surface / selected-surface)
148
+ S + 0.12 hovering the active one (current-surface-hover)
111
149
  ```
112
150
 
113
151
  ## Consolidation: tokens that go away
@@ -180,8 +218,8 @@ Each provides `bg`, `bg-hover`, `surface`, `fg`, `text`, `border`.
180
218
 
181
219
  ## Adding a new token
182
220
 
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.
221
+ 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-surface-<level>` rather than a raw scale value.
222
+ 2. Does it represent a new named surface, state, or status? Add it to `surfaces.css` (surfaces/aspects) or `roles.css` (everything else), referencing scale or hue-role tokens — never raw hex.
185
223
  3. Follow the suffix order: `{name}-{part}[-{state}]`.
186
224
  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
225
  5. If it ships state variants (`-hover`, `-active`), declare them adjacent to the base in the same block.
@@ -5,6 +5,36 @@
5
5
  @layer dx-base {
6
6
  :root {
7
7
  color-scheme: light;
8
+ /* Direction an aspect moves off its host surface: away from the canvas in either theme.
9
+ * Aspects multiply their offset by this instead of branching on light-dark(), which resolves
10
+ * unreliably when nested inside a relative-color expression. */
11
+ --dx-lift: -1;
12
+ /*
13
+ * Light attenuation.
14
+ *
15
+ * Lightness is not perceptually symmetric: subtracting 0.10 L from a near-white surface reads
16
+ * far heavier than adding 0.10 to a near-black one. Every aspect family therefore multiplies
17
+ * its offset by a factor declared here — dark sets them all to 1, so the dark appearance is the
18
+ * reference and light is tuned against it. One family, one factor, one place.
19
+ *
20
+ * Family Offsets (dark) Light Light dL on the canvas
21
+ * state .02 / .08 / .10 / .12 0.5 hover .04, selected .05
22
+ * well .05 0.5 input fill .025
23
+ * separator .089 / .115 / .064 0.75 subdued .067, input .086
24
+ * sep-line .166 0.45 .075 — near the floor; subdued is .067
25
+ * bar .025 1 toolbars read correctly as-is
26
+ * placeholder .30 1 ink, not a surface; wants the contrast
27
+ * scrollbar .23 / .30 / .34 1 was invisible in light before deriving
28
+ *
29
+ * Adjust a family by changing its factor, not by editing offsets — those are the dark contract.
30
+ */
31
+ --dx-attenuate-state: 0.5;
32
+ --dx-attenuate-well: 0.5;
33
+ --dx-attenuate-separator: 0.75;
34
+ --dx-attenuate-separator-line: 0.45;
35
+ --dx-attenuate-bar: 1;
36
+ --dx-attenuate-placeholder: 1;
37
+ --dx-attenuate-scrollbar: 1;
8
38
  touch-action: pan-x pan-y;
9
39
  font-synthesis: none;
10
40
  font-variation-settings:
@@ -17,6 +47,15 @@
17
47
 
18
48
  .dark {
19
49
  color-scheme: dark;
50
+ --dx-lift: 1;
51
+ /* Dark is the reference: every family runs at full strength. */
52
+ --dx-attenuate-state: 1;
53
+ --dx-attenuate-well: 1;
54
+ --dx-attenuate-separator: 1;
55
+ --dx-attenuate-separator-line: 1;
56
+ --dx-attenuate-bar: 1;
57
+ --dx-attenuate-placeholder: 1;
58
+ --dx-attenuate-scrollbar: 1;
20
59
  }
21
60
 
22
61
  button {
@@ -10,12 +10,16 @@
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-[2rem] px-3;
13
+ @apply shrink-0 inline-flex select-none items-center justify-center overflow-hidden;
14
+ /* Geometry comes from the density knobs, not from utilities in the class list: `utilities` is
15
+ * the last cascade layer, so any `px-*`/`h-*` a theme emits would silently beat a rule here. */
16
+ min-block-size: var(--dx-control);
17
+ padding-inline: var(--dx-control-pad);
14
18
  @apply font-medium transition-colors duration-100 ease-linear bg-input-bg;
15
19
  /* The default variant inherits ambient valence colors when rendered inside a valence surface
16
20
  * (e.g. Message.Root, which sets --dx-valence-*); otherwise it falls back to the base input styling. */
17
21
  &[data-variant='default'] {
18
- color: var(--dx-valence-text, inherit);
22
+ /* color: var(--dx-valence-text, inherit); */
19
23
  background: var(--dx-valence-bg, var(--color-input-bg));
20
24
  }
21
25
  /* Toggle / tab group: the selected item reads as standard text on a surface-relative fill (selected-surface
@@ -64,8 +68,17 @@
64
68
  @apply bg-hover-surface;
65
69
  }
66
70
 
71
+ /*
72
+ * Light moves the button's OWN fill: the host-derived hover sits above the fill here, so it
73
+ * lightened on hover and landed on `--color-selected-surface`. Dark keeps the host tint — the
74
+ * dark ladder puts it clear of the fill already, and the token cannot carry it: declared at
75
+ * `:root`, `var(--color-hover-surface)` would freeze there and stop tracking the zone.
76
+ */
67
77
  &[data-variant='default']:hover {
68
78
  color: var(--dx-valence-text);
79
+ background-color: var(--dx-valence-bg-hover, var(--color-input-bg-hover));
80
+ }
81
+ .dark &[data-variant='default']:hover {
69
82
  background-color: var(--dx-valence-bg-hover, var(--color-hover-surface));
70
83
  }
71
84
 
@@ -116,21 +129,13 @@
116
129
  .dx-button:not([data-props~='wrap']) {
117
130
  @apply truncate;
118
131
  }
119
- .dx-button[data-density='lg'] {
120
- @apply min-h-[2.5rem] px-3;
121
- }
132
+ /* `data-density` is handled by the shared density rule in theme/spacing.css, which sets
133
+ * --dx-control / --dx-control-pad; only the type-scale tweak is button-specific. */
122
134
  .dx-button[data-density='sm'] {
123
- @apply min-h-[1.75rem] px-2;
135
+ @apply text-xs;
124
136
  }
125
- .dx-button[data-density='xs'] {
126
- @apply min-h-[1.5rem] px-1 text-xs;
127
- }
128
- .dx-button[data-density='xs'].aspect-square {
129
- @apply size-6 px-0;
130
- }
131
- @media (pointer: fine) {
132
- .dx-button[data-density='md'] {
133
- @apply min-h-[2rem] px-2.5;
134
- }
137
+ .dx-button.aspect-square {
138
+ padding-inline: 0;
139
+ inline-size: var(--dx-control);
135
140
  }
136
141
  }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Callout — hue-tinted panel: applies bg-{hue}-surface / border-{hue}-border / text-{hue}-fg via a
3
+ * data-hue attribute (on the element itself or an ancestor) or a BEM modifier.
4
+ * Example: <div className='dx-callout' data-hue='blue'>...</div>
5
+ * Example: <div className='dx-callout dx-callout--blue'>...</div>
6
+ */
7
+
8
+ @layer dx-components {
9
+ .dx-callout[data-hue='neutral'],
10
+ :where([data-hue='neutral']) .dx-callout,
11
+ .dx-callout--neutral {
12
+ @apply bg-neutral-surface border-neutral-border text-neutral-fg;
13
+ }
14
+
15
+ .dx-callout[data-hue='red'],
16
+ :where([data-hue='red']) .dx-callout,
17
+ .dx-callout--red {
18
+ @apply bg-red-surface border-red-border text-red-fg;
19
+ }
20
+
21
+ .dx-callout[data-hue='orange'],
22
+ :where([data-hue='orange']) .dx-callout,
23
+ .dx-callout--orange {
24
+ @apply bg-orange-surface border-orange-border text-orange-fg;
25
+ }
26
+
27
+ .dx-callout[data-hue='amber'],
28
+ :where([data-hue='amber']) .dx-callout,
29
+ .dx-callout--amber {
30
+ @apply bg-amber-surface border-amber-border text-amber-fg;
31
+ }
32
+
33
+ .dx-callout[data-hue='yellow'],
34
+ :where([data-hue='yellow']) .dx-callout,
35
+ .dx-callout--yellow {
36
+ @apply bg-yellow-surface border-yellow-border text-yellow-fg;
37
+ }
38
+
39
+ .dx-callout[data-hue='lime'],
40
+ :where([data-hue='lime']) .dx-callout,
41
+ .dx-callout--lime {
42
+ @apply bg-lime-surface border-lime-border text-lime-fg;
43
+ }
44
+
45
+ .dx-callout[data-hue='green'],
46
+ :where([data-hue='green']) .dx-callout,
47
+ .dx-callout--green {
48
+ @apply bg-green-surface border-green-border text-green-fg;
49
+ }
50
+
51
+ .dx-callout[data-hue='emerald'],
52
+ :where([data-hue='emerald']) .dx-callout,
53
+ .dx-callout--emerald {
54
+ @apply bg-emerald-surface border-emerald-border text-emerald-fg;
55
+ }
56
+
57
+ .dx-callout[data-hue='teal'],
58
+ :where([data-hue='teal']) .dx-callout,
59
+ .dx-callout--teal {
60
+ @apply bg-teal-surface border-teal-border text-teal-fg;
61
+ }
62
+
63
+ .dx-callout[data-hue='cyan'],
64
+ :where([data-hue='cyan']) .dx-callout,
65
+ .dx-callout--cyan {
66
+ @apply bg-cyan-surface border-cyan-border text-cyan-fg;
67
+ }
68
+
69
+ .dx-callout[data-hue='sky'],
70
+ :where([data-hue='sky']) .dx-callout,
71
+ .dx-callout--sky {
72
+ @apply bg-sky-surface border-sky-border text-sky-fg;
73
+ }
74
+
75
+ .dx-callout[data-hue='blue'],
76
+ :where([data-hue='blue']) .dx-callout,
77
+ .dx-callout--blue {
78
+ @apply bg-blue-surface border-blue-border text-blue-fg;
79
+ }
80
+
81
+ .dx-callout[data-hue='indigo'],
82
+ :where([data-hue='indigo']) .dx-callout,
83
+ .dx-callout--indigo {
84
+ @apply bg-indigo-surface border-indigo-border text-indigo-fg;
85
+ }
86
+
87
+ .dx-callout[data-hue='violet'],
88
+ :where([data-hue='violet']) .dx-callout,
89
+ .dx-callout--violet {
90
+ @apply bg-violet-surface border-violet-border text-violet-fg;
91
+ }
92
+
93
+ .dx-callout[data-hue='purple'],
94
+ :where([data-hue='purple']) .dx-callout,
95
+ .dx-callout--purple {
96
+ @apply bg-purple-surface border-purple-border text-purple-fg;
97
+ }
98
+
99
+ .dx-callout[data-hue='fuchsia'],
100
+ :where([data-hue='fuchsia']) .dx-callout,
101
+ .dx-callout--fuchsia {
102
+ @apply bg-fuchsia-surface border-fuchsia-border text-fuchsia-fg;
103
+ }
104
+
105
+ .dx-callout[data-hue='pink'],
106
+ :where([data-hue='pink']) .dx-callout,
107
+ .dx-callout--pink {
108
+ @apply bg-pink-surface border-pink-border text-pink-fg;
109
+ }
110
+
111
+ .dx-callout[data-hue='rose'],
112
+ :where([data-hue='rose']) .dx-callout,
113
+ .dx-callout--rose {
114
+ @apply bg-rose-surface border-rose-border text-rose-fg;
115
+ }
116
+
117
+ .dx-callout[data-hue='info'],
118
+ :where([data-hue='info']) .dx-callout,
119
+ .dx-callout--info {
120
+ @apply bg-info-surface border-info-border text-info-fg;
121
+ }
122
+
123
+ .dx-callout[data-hue='success'],
124
+ :where([data-hue='success']) .dx-callout,
125
+ .dx-callout--success {
126
+ @apply bg-success-surface border-success-border text-success-fg;
127
+ }
128
+
129
+ .dx-callout[data-hue='warning'],
130
+ :where([data-hue='warning']) .dx-callout,
131
+ .dx-callout--warning {
132
+ @apply bg-warning-surface border-warning-border text-warning-fg;
133
+ }
134
+
135
+ .dx-callout[data-hue='error'],
136
+ :where([data-hue='error']) .dx-callout,
137
+ .dx-callout--error {
138
+ @apply bg-error-surface border-error-border text-error-fg;
139
+ }
140
+ }
@@ -0,0 +1,36 @@
1
+ @layer dx-components {
2
+ /*
3
+ * Shared input-box treatment — the hairline-bordered surface used by text inputs and by CodeMirror
4
+ * editors styled to match them (`Input` default variant, `MarkdownField`, `RefEditor`, the inbox
5
+ * composer `Editor`). Single source for the whole chrome: surface + border + radius + padding,
6
+ * the focus-within colour shift, and the subtle focus ring (mirrors `dx-focus-ring`) — applied
7
+ * whether the box itself is the focusable or it wraps an inner focusable (CodeMirror
8
+ * contenteditable, adorned field). Callers add only sizing (height/min-height) and layout;
9
+ * element paddings from density utilities (`px-*`/`py-0`) still win over the defaults here.
10
+ */
11
+ .dx-input {
12
+ @apply transition-colors border border-input-separator rounded-xs p-1 px-2;
13
+
14
+ /*
15
+ * A zone, not a bare fill: the input publishes its own surface so what sits on it — the
16
+ * placeholder above all — derives from the input's colour rather than from the host behind it.
17
+ * `@apply` cannot take a zone class, hence the two lines rather than `dx-input-surface`.
18
+ */
19
+ --surface-bg: var(--color-input-surface);
20
+ background-color: var(--surface-bg);
21
+
22
+ &:focus {
23
+ @apply outline-hidden;
24
+ }
25
+
26
+ &:focus-within {
27
+ @apply bg-focus-surface border-separator;
28
+ }
29
+
30
+ /* Border matches the ring while it shows, so the focus treatment reads as one band (no inner line). */
31
+ &:not([disabled]):focus-visible,
32
+ &:not([disabled]):has(:focus-visible) {
33
+ @apply ring-focus-line ring-offset-focus-offset z-[1] ring-[var(--color-focus-ring-subtle)] border-[color:var(--color-focus-ring-subtle)];
34
+ }
35
+ }
36
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Selected / Current / Highlighted state utilities.
3
3
  *
4
- * See `./selected.md` for the ARIA pairing grammar — every class below
4
+ * See `./state.md` for the ARIA pairing grammar — every class below
5
5
  * fires off a specific attribute and silently does nothing without it.
6
6
  */
7
7
 
@@ -23,13 +23,6 @@
23
23
  hover:aria-selected:bg-selected-surface-hover hover:aria-selected:text-selected-fg
24
24
  hover:aria-[current=true]:bg-current-surface-hover hover:aria-[current=true]:text-current-fg;
25
25
  }
26
- .dx-hover-row {
27
- @apply group-hover/row:bg-hover-surface-subtle! group-hover/row:text-hover-fg!
28
- group-hover/row:group-aria-selected/row:bg-selected-surface-hover!
29
- group-hover/row:group-aria-selected/row:text-selected-fg!
30
- group-hover/row:group-aria-[current=true]/row:bg-current-surface-hover!
31
- group-hover/row:group-aria-[current=true]/row:text-current-fg!;
32
- }
33
26
 
34
27
  /*
35
28
  * Pairs with `aria-selected`. Use for listbox/option selection (master/detail).
@@ -61,9 +54,6 @@
61
54
  aria-[current=true]:bg-current-surface! aria-[current=true]:text-current-fg!
62
55
  aria-[current=true]:after:ring-focus-ring-subtle!;
63
56
  }
64
- .dx-current-row {
65
- @apply group-aria-[current=true]/row:bg-current-surface! group-aria-[current=true]/row:text-current-fg!;
66
- }
67
57
 
68
58
  /*
69
59
  * Pairs with Radix-managed `data-highlighted`. Don't set the attribute manually.
@@ -1,6 +1,6 @@
1
1
  # `dx-*` selection / navigation grammar
2
2
 
3
- Companion to `selected.css`. The four selection-state utilities below are
3
+ Companion to `state.css`. The four selection-state utilities below are
4
4
  each bound to a specific ARIA attribute (or `data-*`) selector. **The
5
5
  class only fires when the matching ARIA attribute is set on the same
6
6
  element.** Mismatches (e.g. `dx-current` paired with `aria-selected`)