actual-css 0.4.1 → 0.5.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 (74) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +2 -2
  3. package/dist/actual.css +53 -14
  4. package/dist/actual.full.css +300 -91
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/dist/actual.min.css +1 -1
  8. package/docs/design-notes/spacing-units.md +286 -0
  9. package/docs/navigation.json +1 -0
  10. package/docs/pages/components/alert.md +1 -1
  11. package/docs/pages/components/app-navigation.md +2 -0
  12. package/docs/pages/components/badge.md +17 -3
  13. package/docs/pages/components/button.md +9 -2
  14. package/docs/pages/components/card.md +13 -0
  15. package/docs/pages/components/data-list.md +72 -0
  16. package/docs/pages/components/dialog.md +2 -0
  17. package/docs/pages/components/drawer.md +2 -0
  18. package/docs/pages/components/fab.md +2 -0
  19. package/docs/pages/components/meter.md +56 -1
  20. package/docs/pages/components/progress.md +10 -0
  21. package/docs/pages/components/skeleton.md +2 -0
  22. package/docs/pages/components/steps.md +10 -2
  23. package/docs/pages/components/table.md +1 -0
  24. package/docs/pages/enhancements/flyout.md +2 -0
  25. package/docs/pages/enhancements/status-bar.md +10 -1
  26. package/docs/pages/examples/overview.md +2 -1
  27. package/docs/pages/forms/joined-controls.md +2 -0
  28. package/docs/pages/forms/overview.md +1 -1
  29. package/docs/pages/forms/range.md +2 -0
  30. package/docs/pages/forms/states-and-sizes.md +1 -1
  31. package/docs/pages/forms/validation.md +2 -0
  32. package/docs/pages/foundations/philosophy.md +11 -1
  33. package/docs/pages/foundations/tokens.md +7 -3
  34. package/docs/pages/foundations/typography.md +10 -2
  35. package/docs/pages/guides/introduction.md +1 -1
  36. package/docs/pages/layout/scroll-snap.md +2 -0
  37. package/docs/pages/layout/sidebar-layout.md +2 -0
  38. package/docs/pages/layout/topbar.md +2 -0
  39. package/docs/pages/utilities/accessibility.md +18 -0
  40. package/package.json +1 -1
  41. package/scripts/reserved-classes.json +4 -0
  42. package/src/css/components/actions.css +1 -0
  43. package/src/css/components/breadcrumb.css +1 -0
  44. package/src/css/components/data-list.css +27 -0
  45. package/src/css/components/drawer.css +30 -11
  46. package/src/css/components/flyout.css +17 -12
  47. package/src/css/components/index.css +1 -0
  48. package/src/css/components/meter.css +28 -1
  49. package/src/css/components/modal.css +28 -11
  50. package/src/css/components/navbar.css +2 -0
  51. package/src/css/components/pagination.css +1 -0
  52. package/src/css/components/progress.css +5 -0
  53. package/src/css/components/status-bar.css +31 -15
  54. package/src/css/components/steps.css +36 -9
  55. package/src/css/components/surface.css +5 -3
  56. package/src/css/components/tab.css +1 -0
  57. package/src/css/components/tooltip.css +5 -8
  58. package/src/css/core/base.css +7 -1
  59. package/src/css/core/intents.css +6 -0
  60. package/src/css/core/tokens.css +23 -9
  61. package/src/css/core/variants.css +17 -4
  62. package/src/css/forms/form-actions.css +1 -0
  63. package/src/css/forms/form.css +4 -4
  64. package/src/css/layout/cluster.css +1 -0
  65. package/src/css/layout/scroll-snap.css +1 -0
  66. package/src/css/layout/sidebar-layout.css +1 -0
  67. package/src/css/layout/switcher.css +1 -0
  68. package/src/css/layout/topbar.css +1 -0
  69. package/src/css/typography/prose.css +2 -3
  70. package/src/css/utilities/base.css +16 -0
  71. package/src/js/selectors.js +1 -0
  72. package/src/js/status.js +88 -7
  73. package/src/js/surface.js +2 -11
  74. package/src/js/transition.js +20 -0
@@ -6,13 +6,21 @@ Use an `<ol>` because order is part of the meaning. Add `.complete` to finished
6
6
  steps and `aria-current="step"` to exactly one current step. Unmarked items are
7
7
  future steps. The component needs no JavaScript.
8
8
 
9
+ The three states are distinct without any glyph: future is neutral, complete is
10
+ a filled marker with an accented connector, current is an outlined marker with a
11
+ thicker ring. The accented connector reads as "already walked", so the segment
12
+ leaving the current step stays neutral. A step carrying both states renders
13
+ entirely as current — marker, glyph, and connector.
14
+
15
+ **Related terms:** stepper, progress steps, step indicator, wizard steps, multi-step progress.
16
+
9
17
  ## Class reference
10
18
 
11
19
  | Class | Kind | Description |
12
20
  |-------------------------|-----------|--------------------------------------------|
13
21
  | `.steps` | Component | Ordered progression with numbered markers. |
14
- | `.complete` | State | A finished step. |
15
- | `[aria-current="step"]` | State | The current step. |
22
+ | `.complete` | State | A finished step: filled marker. |
23
+ | `[aria-current="step"]` | State | The current step: outlined marker. |
16
24
 
17
25
  ```html demo
18
26
  <ol class="steps">
@@ -7,6 +7,7 @@
7
7
  - Use `.text-end` for numeric columns and `.text-nowrap` for non-wrapping cells.
8
8
  - For wide tables, make the wrapper an accessible scroll region with `role="region"`, `aria-labelledby`, and `tabindex="0"`.
9
9
  - Tables stay content-first and neutral by default.
10
+ - `.table` is a static, content-first table. It provides no sorting, filtering, search, selection, pagination, or row actions. For an interactive data grid ("datatable"), use a dedicated component — see the [data grid template](../../demo/templates/data-grid.html), which themes `data-grid-component` with Actual CSS tokens.
10
11
  - `.table` does not consume shared variants or intent colors (`--ui-bg` / `--intent`): a `.soft` + intent combo on a `<tr>` has no effect. To tint rows or headers, style them with a class of your own.
11
12
 
12
13
  ## Class reference
@@ -2,6 +2,8 @@
2
2
 
3
3
  > Positioned surface attached to a trigger, commonly used for action lists and small panels.
4
4
 
5
+ **Related terms:** popover, dropdown, dropdown menu, action menu, context menu, bottom sheet, action sheet, anchored panel.
6
+
5
7
  ## Class reference
6
8
 
7
9
  | Class | Kind | Description |
@@ -11,9 +11,18 @@ replace the previous one. Keep one element in the HTML, styled with
11
11
  `.status-bar` and carrying the `[data-status][role="status"]` contract that the
12
12
  runtime targets, empty by default.
13
13
 
14
- - JavaScript only updates its text content; it shows when non-empty and hides when empty, while staying available as a live region.
14
+ - JavaScript writes the text content and toggles `.is-open`, the state class the CSS animates. The element is never hidden, so it stays available as a live region.
15
15
  - Use it for transient status (`Saved.`, `Reconnected.`, `Copied.`). Critical, persistent, or actionable information belongs in `.alert`, inline messages, or dialogs.
16
16
  - Intents: `danger`, `success`, `warning`, `neutral`. The default (no intent) is a neutral dark pill.
17
+ - On mobile, the runtime keeps the bar above the software keyboard by tracking `visualViewport` while a message is showing.
18
+
19
+ Clearing is deferred, not immediate: the bar closes first and only empties its
20
+ text and intent once the exit transition has finished, so it leaves at the size
21
+ and color it was shown at. A new message during that exit wins — it cancels the
22
+ pending cleanup instead of being wiped by it. Read the bar's content right after
23
+ `clear()` and you still see the outgoing message; that is the contract, not a
24
+ race. An empty message clears too, so `status(response.error)` on a successful
25
+ response closes the bar rather than opening a blank pill.
17
26
 
18
27
  The runtime auto-wires the status bar to Actual's form validation: a form that
19
28
  fails to submit shows its `data-validation-message` in the status bar with the
@@ -4,7 +4,8 @@
4
4
 
5
5
  The examples live in the repository under `demo/` and load `actual.full.css`
6
6
  and `actual.full.js` from `dist/`, plus the theme palettes bundle from
7
- `demo/assets/` (`bun run build:themes`).
7
+ `demo/assets/`. The bundle is committed and regenerated by
8
+ `bun run build:themes`, so the pages open from a fresh checkout.
8
9
 
9
10
  ## Templates
10
11
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  > Use `.join` when controls need to be visually joined into a single unit.
4
4
 
5
+ **Related terms:** segmented control, button group, input group.
6
+
5
7
  ## Class reference
6
8
 
7
9
  | Class | Kind | Description |
@@ -164,7 +164,7 @@ A full form using the control classes, field wrappers, and form actions:
164
164
  - `required` is the semantic source of truth. Add `<span class="required-mark" aria-hidden="true">*</span>` inside a label only when a visual required marker is wanted; the framework does not add one automatically.
165
165
  - `.field` is the canonical field wrapper. It works on `<label>` and `<div>`.
166
166
  - `.field-label` is element-agnostic. Use it on `<span>` inside a wrapped label, on `<label for="…">` in a detached layout, or on `<legend>` inside a fieldset.
167
- - `.field-group` styles a `<fieldset class="field-group">` as a grouped field container. Its legend gutter is tunable with `--fieldset-legend-padding-inline` (`var(--space-10)`).
167
+ - `.field-group` styles a `<fieldset class="field-group">` as a grouped field container. Its legend gutter is tunable with `--fieldset-legend-padding-inline` (`var(--space-10)`). It carries no sibling margin: space consecutive groups with `.stack` on the form, so the rhythm follows the layout instead of the component.
168
168
  - Use native `<optgroup>` and `disabled` options in `.select` controls when applicable; the customizable picker preserves both as a progressive enhancement.
169
169
  - `.choice` is the choice-label API. Use `.check` or `.radio` on the nested control. The control goes first, the label group second, so multi-line labels align the control with the first line.
170
170
  - For a switch, use `class="switch"` and `role="switch"`.
@@ -4,6 +4,8 @@
4
4
 
5
5
  Core: native range, enhanced by `accent-color`. Optional richer skin may come later.
6
6
 
7
+ **Related terms:** slider, range input, scrubber.
8
+
7
9
  ```html demo
8
10
  <label class="field">
9
11
  <span class="field-label">Volume</span>
@@ -21,7 +21,7 @@ without its label would only break the alignment. Scale them by scaling their
21
21
  type instead.
22
22
 
23
23
  ```html demo
24
- <form novalidate>
24
+ <form class="stack" novalidate>
25
25
  <fieldset class="field-group">
26
26
  <legend class="field-label">Text control states</legend>
27
27
  <div class="stack" style="--grid-min: 14rem;">
@@ -2,6 +2,8 @@
2
2
 
3
3
  > Inline error feedback that respects assistive technology and form state.
4
4
 
5
+ **Related terms:** inline validation, field error.
6
+
5
7
  ## Class reference
6
8
 
7
9
  | Class | Kind | Description |
@@ -10,9 +10,19 @@ Components are semantic boxes: `.btn`, `.badge`, `.alert`, `.card`, `.avatar`, e
10
10
 
11
11
  `.primary`, `.secondary`, `.success`, `.warning`, `.danger` do not style elements directly. They only expose `--intent` and `--intent-fg`. Components and variants decide how to consume them.
12
12
 
13
+ Intent classes define the current `--intent` value; they do not form a
14
+ general-purpose color utility system. Use `.intent-color` when a simple element
15
+ should consume the current intent as its foreground color. It works with an
16
+ intent on the same element or inherited from an ancestor.
17
+
13
18
  ```html demo
14
19
  <button class="btn primary">Primary</button>
15
20
  <span class="badge success">Success</span>
21
+ <span class="success intent-color">Success text</span>
22
+ <span class="danger">
23
+ <i class="ti ti-alert-triangle intent-color" aria-hidden="true"></i>
24
+ Something needs attention
25
+ </span>
16
26
  ```
17
27
 
18
28
  There is no `.btn-primary` or `.badge-success`. Intents are generic and reusable across any component that supports them.
@@ -35,7 +45,7 @@ Custom intents need no framework support — define `--intent` and `--intent-fg`
35
45
 
36
46
  ## Variants
37
47
 
38
- `.solid`, `.soft`, `.outline` define the visual treatment by setting `--ui-bg`, `--ui-fg`, `--ui-border`. Components consume these tokens with sensible fallbacks. Interactive components also set a `--ui-hover-bg` recipe that their hover rule reads.
48
+ `.solid`, `.soft`, `.outline`, `.surface` define the visual treatment by setting `--ui-bg`, `--ui-fg`, `--ui-border`. Components consume these tokens with sensible fallbacks. Interactive components also set a `--ui-hover-bg` recipe that their hover rule reads.
39
49
 
40
50
  `.btn` defaults to `.solid`; `.badge` and `.alert` default to `.soft`.
41
51
 
@@ -133,7 +133,7 @@ Spacing should be useful without becoming a design-token encyclopedia.
133
133
  }
134
134
  ```
135
135
 
136
- Official steps are named in tens so the scale stays extensible: Actual CSS only publishes multiples of 10, and applications can intercalate intermediate values (`--space-15`, `--space-45`, …) without colliding with a future framework step.
136
+ Official steps are named in tens so the scale stays extensible. **Actual CSS will never define a `--space-*` token that is not a multiple of 10.** The intermediate names (`--space-15`, `--space-45`, …) are reserved for application-level interpolation: defining them is not writing into the framework namespace, it is using the half of the scale the framework has permanently given up.
137
137
 
138
138
  Use `em` for spacing that should scale with the component text size, such as button gaps and inline padding. Use `rem` for global rhythm, layouts, and fixed density.
139
139
 
@@ -220,7 +220,7 @@ With `color-mix()` support the shadows derive from `--shadow-color` (a theme tok
220
220
 
221
221
  ### Motion
222
222
 
223
- Motion tokens keep transitions consistent and easy to disable.
223
+ Motion durations keep transitions consistent and easy to disable.
224
224
 
225
225
  ```css
226
226
  :root {
@@ -229,10 +229,13 @@ Motion tokens keep transitions consistent and easy to disable.
229
229
  --duration-slow: 200ms; /* one-shot open/close */
230
230
  --duration-spin: 0.75s; /* continuous spin (.spinner) */
231
231
  --duration-shimmer: 1.2s; /* continuous shimmer (.skeleton, indeterminate .progress) */
232
- --ease: ease;
232
+ --ease-enter: cubic-bezier(0.2, 0, 0, 1);
233
+ --ease-exit: cubic-bezier(0.4, 0, 1, 1);
233
234
  }
234
235
  ```
235
236
 
237
+ Ordinary interaction/state transitions have **no easing token**: they use the CSS default `ease` directly, and the dialog shake feedback plus the `actual-dialog` View Transition keep that default deliberately. The presence pair exists only for one-shot open/close where open and closed states own separate transition declarations (`.status-bar`/`.is-open`, `dialog.modal[open]`, `dialog.drawer[open]`): `--ease-enter` decelerates the appearance, `--ease-exit` accelerates the departure. The pair follows Material's standard rhythm; the emphasized-decelerate entrance is deliberately not offered because it only pays off at 250–400ms durations, beyond this vocabulary. Backdrops keep `ease` — a scrim only fades, it does not travel — but track their surface's duration. Toggles that share one transition list at `--duration-fast` (flyout, tooltip) also stay on `ease`: at 100ms the curve barely resolves and entry/exit cannot split on a single rule. These absences are deliberate, not gaps; a theme with a different motion character overrides `--ease-enter` / `--ease-exit`.
238
+
236
239
  `reset.css` clamps raw animation and transition durations to `0.01ms`, resets their delays, and restores `html` scroll behavior under `prefers-reduced-motion: reduce`. Decorative loops effectively complete immediately without blanket-disabling animation support.
237
240
 
238
241
  ### Icons
@@ -294,6 +297,7 @@ The shared visual variants are:
294
297
  - `.solid`: full background, paired foreground. This is the default for buttons.
295
298
  - `.soft`: lightly tinted background, colored text. Good for alerts, badges, and secondary actions.
296
299
  - `.outline`: transparent background, colored border and text.
300
+ - `.surface`: the page surface with the theme border, and the intent only in the text. The neutral chrome neither `.soft` (the intent-tinted treatment) nor `.outline` (no fill) can give.
297
301
 
298
302
  Buttons also support two button-only variants:
299
303
 
@@ -77,7 +77,15 @@ There is also a non-fluid `.lead` utility in the core, for a simple readable int
77
77
 
78
78
  Global links are intentionally neutral. Only `.prose` styles them as prose links, and only components style their own link-like elements (nav links, button-as-link, tabs, breadcrumbs, etc.). The reason is that underlining every `a` globally fights against button-like links, nav links, clickable cards, menu items, and tab triggers. Components that need link semantics should opt in with their own rule.
79
79
 
80
- Prose links take `--primary`. That breaks down for a theme whose primary is a light or fully saturated accent: it can carry a filled button, where it is the background under its own `--primary-fg`, but not inline body text, where it has to hold contrast against `--surface` on its own. `--link` is the optional override for exactly that case — a theme sets it and `.prose` link ink follows, hover included, with no rule to restate. Unset, it falls back to `--primary`, so existing themes are unaffected.
80
+ `--link` is the theme-level link ink. It is unset by default, so a bare `a` inherits its color and `.prose` links fall back to `--primary` — nothing changes until a theme opts in. Setting it is that opt-in, and it reaches both places at once:
81
+
82
+ ```css
83
+ :root {
84
+ --link: var(--primary); /* bare links become link-colored, prose included */
85
+ }
86
+ ```
87
+
88
+ The `.prose` fallback to `--primary` breaks down for a theme whose primary is a light or fully saturated accent: it can carry a filled button, where it is the background under its own `--primary-fg`, but not inline body text, where it has to hold contrast against `--surface` on its own. `--link` is the override for exactly that case — hover included, with no rule to restate.
81
89
 
82
90
  ```css
83
91
  [data-theme="vivid"] {
@@ -133,7 +141,7 @@ There is no `--line-height-normal`. It would be redundant with `--line-height`.
133
141
  - `--prose-line-height` — body line height inside `.prose`.
134
142
  - `--prose-heading-line-height` — heading line height inside `.prose`.
135
143
  - `--prose-flow` — vertical rhythm between sibling blocks inside `.prose`.
136
- - `--link` — prose link ink; falls back to `--primary` when a theme leaves it unset.
144
+ - `--link` — theme-level link ink, listed here because `.prose` is its main consumer. Unset, bare links inherit and prose links fall back to `--primary`.
137
145
 
138
146
  ## text-wrap
139
147
 
@@ -75,7 +75,7 @@ Actual CSS uses a small unprefixed class grammar:
75
75
  ```
76
76
 
77
77
  Intents are `.primary`, `.secondary`, `.success`, `.warning`, and `.danger`, and
78
- work across components. Variants such as `.solid`, `.soft`, and `.outline` are
78
+ work across components. Variants such as `.solid`, `.soft`, `.outline`, and `.surface` are
79
79
  shared by buttons, badges, alerts, and cards; `.inverted` is a shared surface
80
80
  modifier that paints any block with the inverse surface. `.ghost` and `.link`
81
81
  are button-only. Size variants `.sm` and `.lg` scale controls consistently.
@@ -6,6 +6,8 @@ Horizontal rails that stay useful without JavaScript and snap in modern browsers
6
6
 
7
7
  Use `.scroll-snap` for a niche row of items that should scroll horizontally, such as card rails, media strips, overflow tabs, or a simple touch-friendly carousel baseline. Scrolling only appears when the items are wider than the container; set `--scroll-snap-item-size` for card rails.
8
8
 
9
+ **Related terms:** carousel, card carousel, horizontal scroller, media rail, scrolling rail.
10
+
9
11
  ```html demo
10
12
  <section class="stack" aria-labelledby="featured-title">
11
13
  <h2 id="featured-title">Featured articles</h2>
@@ -5,6 +5,8 @@ switches intrinsically according to its own available width: the regions sit
5
5
  side by side when the main minimum, preferred aside width, and gap fit; they
6
6
  otherwise stack. No `.container-query` ancestor is required.
7
7
 
8
+ **Related terms:** master-detail, split view.
9
+
8
10
  ```html demo
9
11
  <div class="sidebar-layout">
10
12
  <article class="stack">
@@ -21,3 +21,5 @@ Use `.topbar` for the structural shell of an app-shell header: sticky positionin
21
21
  ```
22
22
 
23
23
  This is for the app-shell topbar specifically — a structural need that recurs unchanged across app UIs regardless of brand. It is not for marketing or editorial site headers, which are one-off visual identity per page.
24
+
25
+ **Related terms:** sticky header, app bar, application header.
@@ -32,3 +32,21 @@ Use `.sr-only` for text that should be available to assistive technology but vis
32
32
  border: 0;
33
33
  }
34
34
  ```
35
+
36
+ ## Touch targets
37
+
38
+ `.touch-target` adds 8px of invisible hit area around a small control on coarse
39
+ primary pointers, without growing the visual. Apply it opt-in to targets that
40
+ are genuinely small — icon buttons, close buttons, compact actions.
41
+
42
+ ```html demo
43
+ <button class="btn icon-only touch-target" type="button" aria-label="Close">
44
+ <i class="ti ti-x" aria-hidden="true"></i>
45
+ </button>
46
+ ```
47
+
48
+ **Related terms:** touch target, hit area, tap target, target size.
49
+
50
+ On fine pointers the class is fully neutral. On coarse pointers it establishes
51
+ a relative positioning context — do not apply it to controls whose positioning
52
+ is structural (`fixed`, `absolute`, `sticky`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actual-css",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Plain CSS component framework with semantic classes, universal variants, small tokens, theme hooks, and progressive enhancements.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.4.0",
@@ -79,6 +79,7 @@
79
79
  "container-query",
80
80
  "cursor-pointer",
81
81
  "danger",
82
+ "data-list",
82
83
  "dialog-close",
83
84
  "dialog-confirmation",
84
85
  "dialog-icon",
@@ -134,6 +135,7 @@
134
135
  "inline-size-full",
135
136
  "input",
136
137
  "input-icon",
138
+ "intent-color",
137
139
  "inverted",
138
140
  "is-open",
139
141
  "is-sheet",
@@ -246,6 +248,7 @@
246
248
  "stretch",
247
249
  "subtle",
248
250
  "success",
251
+ "surface",
249
252
  "surface-backdrop",
250
253
  "switch",
251
254
  "switcher",
@@ -269,6 +272,7 @@
269
272
  "textarea",
270
273
  "tooltip",
271
274
  "topbar",
275
+ "touch-target",
272
276
  "truncate",
273
277
  "user-select-none",
274
278
  "visibility-hidden",
@@ -11,6 +11,7 @@
11
11
  --gap: var(--space-20);
12
12
 
13
13
  display: flex;
14
+ flex-direction: row;
14
15
  flex-wrap: wrap;
15
16
  align-items: center;
16
17
  gap: var(--gap);
@@ -6,6 +6,7 @@
6
6
  */
7
7
  .breadcrumb {
8
8
  display: flex;
9
+ flex-direction: row;
9
10
  flex-wrap: wrap;
10
11
  align-items: center;
11
12
  gap: var(--space-10);
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Data list — term/value pairs for the properties of a single object.
3
+ *
4
+ * Expects alternating <dt>/<dd> children: one term, one value per property.
5
+ * A plain <dl> handles richer relationships (several terms or descriptions)
6
+ * and stays untouched by this component.
7
+ *
8
+ * Why margin resets: neutralizes .prose list margins if .data-list appears
9
+ * inside an authored rich-text scope; the grid owns row/column rhythm.
10
+ */
11
+ .data-list {
12
+ display: grid;
13
+ grid-template-columns: max-content minmax(0, 1fr);
14
+ align-items: baseline;
15
+ gap: var(--space-20) var(--space-40);
16
+ }
17
+
18
+ .data-list > dt {
19
+ min-inline-size: 0;
20
+ margin-block-start: 0;
21
+ font-weight: var(--font-weight-strong);
22
+ }
23
+
24
+ .data-list > dd {
25
+ min-inline-size: 0;
26
+ margin: 0;
27
+ }
@@ -13,11 +13,16 @@
13
13
  *
14
14
  * Internal:
15
15
  * --drawer-closed-translate (side/RTL positioning state)
16
+ * --drawer-motion-duration (shared by panel and ::backdrop so exit duration
17
+ * changes in one place; entry state redefines it longer)
16
18
  */
17
19
  dialog.drawer {
18
20
  --drawer-size: 20rem;
19
21
  --drawer-pad: var(--space-40);
20
22
  --drawer-closed-translate: -100%;
23
+ /* Exit is shorter than entry so the panel clears quickly; the scrim reads
24
+ the same variable through inheritance and never lags the panel. */
25
+ --drawer-motion-duration: var(--duration);
21
26
 
22
27
  /* Keep the surface anchored to the viewport in the top layer. This also
23
28
  provides the containing block for the absolutely positioned close button.
@@ -41,16 +46,20 @@ dialog.drawer {
41
46
  opacity: 0;
42
47
  transform: translateX(var(--drawer-closed-translate));
43
48
  transition:
44
- opacity var(--duration-slow),
45
- transform var(--duration-slow);
49
+ opacity var(--drawer-motion-duration) var(--ease-exit),
50
+ transform var(--drawer-motion-duration) var(--ease-exit);
46
51
  }
47
52
 
48
53
  dialog.drawer[open] {
54
+ --drawer-motion-duration: var(--duration-slow);
49
55
  display: grid;
50
56
  grid-template-rows: auto minmax(0, 1fr) auto;
51
57
  gap: var(--space-40);
52
58
  opacity: 1;
53
59
  transform: translateX(0);
60
+ transition:
61
+ opacity var(--drawer-motion-duration) var(--ease-enter),
62
+ transform var(--drawer-motion-duration) var(--ease-enter);
54
63
  }
55
64
 
56
65
  /* Backdrop color sourced from components/surface.css so it matches .modal
@@ -58,7 +67,9 @@ dialog.drawer[open] {
58
67
  dialog.drawer::backdrop {
59
68
  background: var(--backdrop-fill);
60
69
  opacity: 0;
61
- transition: opacity var(--duration-slow);
70
+ /* Scrim curve stays neutral `ease`; only the panel's character curves
71
+ apply. Duration follows --drawer-motion-duration through inheritance. */
72
+ transition: opacity var(--drawer-motion-duration) ease;
62
73
  }
63
74
 
64
75
  dialog.drawer[data-side="end"] {
@@ -94,17 +105,25 @@ dialog.drawer nav {
94
105
  @supports (transition-behavior: allow-discrete) {
95
106
  dialog.drawer {
96
107
  transition:
97
- opacity var(--duration-slow),
98
- transform var(--duration-slow),
99
- overlay var(--duration-slow) allow-discrete,
100
- display var(--duration-slow) allow-discrete;
108
+ opacity var(--drawer-motion-duration) var(--ease-exit),
109
+ transform var(--drawer-motion-duration) var(--ease-exit),
110
+ overlay var(--drawer-motion-duration) allow-discrete,
111
+ display var(--drawer-motion-duration) allow-discrete;
112
+ }
113
+
114
+ dialog.drawer[open] {
115
+ transition:
116
+ opacity var(--drawer-motion-duration) var(--ease-enter),
117
+ transform var(--drawer-motion-duration) var(--ease-enter),
118
+ overlay var(--drawer-motion-duration) allow-discrete,
119
+ display var(--drawer-motion-duration) allow-discrete;
101
120
  }
102
121
 
103
122
  dialog.drawer::backdrop {
104
123
  transition:
105
- opacity var(--duration-slow),
106
- overlay var(--duration-slow) allow-discrete,
107
- display var(--duration-slow) allow-discrete;
124
+ opacity var(--drawer-motion-duration) ease,
125
+ overlay var(--drawer-motion-duration) allow-discrete,
126
+ display var(--drawer-motion-duration) allow-discrete;
108
127
  }
109
128
  }
110
129
 
@@ -140,7 +159,7 @@ dialog.drawer nav {
140
159
 
141
160
  @media (prefers-reduced-motion: no-preference) {
142
161
  dialog.drawer.is-static {
143
- animation: actual-dialog-static var(--duration-slow) var(--ease);
162
+ animation: actual-dialog-static var(--duration-slow) ease;
144
163
  }
145
164
  }
146
165
 
@@ -73,11 +73,10 @@
73
73
  box-shadow: var(--shadow-popout);
74
74
  list-style: none;
75
75
  opacity: 1;
76
- scale: 1;
77
- transition:
78
- opacity var(--duration-fast) var(--ease),
79
- scale var(--duration-fast) var(--ease),
80
- translate var(--duration-fast) var(--ease);
76
+ /* Anchored flyouts fade in and out; scaling around the center would detach
77
+ the anchored edge from its trigger on wide panels. The sheet presentation
78
+ below adds its own translate motion. */
79
+ transition: opacity var(--duration-fast) ease;
81
80
  }
82
81
 
83
82
  .flyout.is-open:not(.is-sheet) {
@@ -87,7 +86,6 @@
87
86
  @starting-style {
88
87
  .flyout.is-open {
89
88
  opacity: 0;
90
- scale: 0.98;
91
89
  }
92
90
  }
93
91
 
@@ -109,22 +107,24 @@
109
107
  max(var(--space-30), env(safe-area-inset-bottom));
110
108
  border-end-start-radius: 0;
111
109
  border-end-end-radius: 0;
110
+ /* Sheet motion is a directional slide from the viewport edge; it replaces
111
+ the base fade-only recipe. */
112
+ transition:
113
+ opacity var(--duration-fast) ease,
114
+ translate var(--duration-fast) ease;
112
115
  }
113
116
 
114
117
  .flyout[hidden] {
115
118
  display: none;
116
119
  opacity: 0;
117
- scale: 0.98;
118
120
  }
119
121
 
120
122
  .flyout.is-sheet[hidden] {
121
- scale: 1;
122
123
  translate: 0 var(--space-30);
123
124
  }
124
125
 
125
126
  @starting-style {
126
127
  .flyout.is-sheet.is-open {
127
- scale: 1;
128
128
  translate: 0 var(--space-30);
129
129
  }
130
130
  }
@@ -132,9 +132,14 @@
132
132
  @supports (transition-behavior: allow-discrete) {
133
133
  .flyout {
134
134
  transition:
135
- opacity var(--duration-fast) var(--ease),
136
- scale var(--duration-fast) var(--ease),
137
- translate var(--duration-fast) var(--ease),
135
+ opacity var(--duration-fast) ease,
136
+ display var(--duration-fast) allow-discrete;
137
+ }
138
+
139
+ .flyout.is-sheet {
140
+ transition:
141
+ opacity var(--duration-fast) ease,
142
+ translate var(--duration-fast) ease,
138
143
  display var(--duration-fast) allow-discrete;
139
144
  }
140
145
  }
@@ -43,6 +43,7 @@
43
43
  @import "./scrollspy.css";
44
44
 
45
45
  /* Data */
46
+ @import "./data-list.css";
46
47
  @import "./list.css";
47
48
  @import "./table.css";
48
49
  @import "./meter.css";
@@ -1,8 +1,16 @@
1
1
  /*
2
- * Meter — native gauge for known-range values.
2
+ * Meter — native and segmented gauges for known-range values.
3
3
  *
4
4
  * Maps the three standard meter zones (optimum / suboptimum / even-less-good)
5
5
  * to the shared success / warning / danger intent colors.
6
+ *
7
+ * Thickness is a dimension, not a density step: the bar always spans its
8
+ * container and only gets thinner or thicker, so --bar-height is the API and
9
+ * .sm/.lg are deliberately not supported.
10
+ *
11
+ * Public hooks:
12
+ * --meter-value
13
+ * --bar-height
6
14
  */
7
15
 
8
16
  .meter {
@@ -17,6 +25,25 @@
17
25
  background: var(--surface-subtle);
18
26
  }
19
27
 
28
+ /* A non-native meter is a weighted flex container. The element type keeps the
29
+ native <meter> API and the composed segmented API distinct without another
30
+ public variant class. */
31
+ .meter:not(meter) {
32
+ --intent: initial;
33
+
34
+ display: flex;
35
+ }
36
+
37
+ .meter:not(meter) > * {
38
+ flex: var(--meter-value, 1) 1 0;
39
+ min-inline-size: 0;
40
+ background: var(--intent);
41
+ }
42
+
43
+ .meter:not(meter) > * + * {
44
+ border-inline-start: var(--border-width) solid var(--surface);
45
+ }
46
+
20
47
  .meter::-webkit-meter-bar {
21
48
  -webkit-appearance: none;
22
49
  block-size: var(--bar-height);