@reliability-design/web 1.4.7 → 1.4.9

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 (48) hide show
  1. package/CHANGELOG.md +98 -2
  2. package/dist/components/rel-accordion/README.md +2 -1
  3. package/dist/components/rel-alert/README.md +1 -1
  4. package/dist/components/rel-autocomplete/README.md +22 -5
  5. package/dist/components/rel-avatar/README.md +9 -7
  6. package/dist/components/rel-badge/README.md +4 -5
  7. package/dist/components/rel-button/README.md +32 -2
  8. package/dist/components/rel-card/README.md +3 -3
  9. package/dist/components/rel-carousel/README.md +3 -0
  10. package/dist/components/rel-checkbox/README.md +8 -4
  11. package/dist/components/rel-chip/README.md +13 -7
  12. package/dist/components/rel-container/README.md +8 -8
  13. package/dist/components/rel-date-picker/README.md +8 -2
  14. package/dist/components/rel-divider/README.md +11 -1
  15. package/dist/components/rel-kbd/README.md +2 -2
  16. package/dist/components/rel-layout/README.md +34 -0
  17. package/dist/components/rel-menu/README.md +3 -1
  18. package/dist/components/rel-menubar/README.md +1 -1
  19. package/dist/components/rel-otp/README.md +4 -3
  20. package/dist/components/rel-pagination/README.md +2 -2
  21. package/dist/components/rel-popover/README.md +5 -5
  22. package/dist/components/rel-progress/README.md +3 -3
  23. package/dist/components/rel-radio/README.md +1 -1
  24. package/dist/components/rel-range-picker/README.md +11 -2
  25. package/dist/components/rel-segmented-button/README.md +7 -5
  26. package/dist/components/rel-select/README.md +11 -6
  27. package/dist/components/rel-sheet/README.md +3 -3
  28. package/dist/components/rel-skeleton/README.md +5 -4
  29. package/dist/components/rel-slider/README.md +3 -2
  30. package/dist/components/rel-snackbar/README.md +13 -7
  31. package/dist/components/rel-stepper/README.md +23 -10
  32. package/dist/components/rel-switch/README.md +16 -7
  33. package/dist/components/rel-tab/README.md +5 -2
  34. package/dist/components/rel-textfield/README.md +19 -2
  35. package/dist/components/rel-time-picker/README.md +6 -0
  36. package/dist/components/rel-toast/README.md +14 -6
  37. package/dist/components/rel-tooltip/README.md +13 -2
  38. package/dist/components/rel-upload/README.md +2 -2
  39. package/dist/index.d.mts +825 -240
  40. package/dist/index.d.ts +825 -240
  41. package/dist/index.js +8617 -5213
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +8424 -5020
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/styles/focus-ring.ts +84 -0
  46. package/dist/styles/vars.css +75 -60
  47. package/dist/{vars-JHTUGNRG.css → vars-DXX6QOAG.css} +75 -60
  48. package/package.json +1 -1
@@ -12,23 +12,23 @@
12
12
  </rel-container>
13
13
 
14
14
  <!-- Elevated surface with small padding -->
15
- <rel-container surface="elevated" padding="sm">
15
+ <rel-container surface="elevated" padding="small">
16
16
  <p>Compact section on the raised surface.</p>
17
17
  </rel-container>
18
18
 
19
19
  <!-- Large padding with shadow -->
20
- <rel-container padding="lg" shadow>
20
+ <rel-container padding="large" shadow>
21
21
  <h1>Hero section</h1>
22
22
  <p>With shadow elevation.</p>
23
23
  </rel-container>
24
24
 
25
25
  <!-- Transparent surface: spacing only, no fill -->
26
- <rel-container surface="transparent" padding="xl">
26
+ <rel-container surface="transparent" padding="extra-large">
27
27
  <div>Content with no background.</div>
28
28
  </rel-container>
29
29
 
30
30
  <!-- Everything combined -->
31
- <rel-container surface="default" padding="md" shadow>
31
+ <rel-container surface="default" padding="medium" shadow>
32
32
  <h3>Card-like container</h3>
33
33
  <p>Page surface, medium padding, shadow.</p>
34
34
  </rel-container>
@@ -40,7 +40,7 @@
40
40
  <!-- background="white|light|none" is the deprecated 1.4.6 spelling of surface="default|elevated|transparent".
41
41
  Still supported; logs once per page:
42
42
  [rel-container] "background" is deprecated and will be removed in 3.0 — use "surface" instead. Map: white → "default", light → "elevated", none → "transparent". -->
43
- <rel-container background="light" padding="sm">
43
+ <rel-container background="light" padding="small">
44
44
  <p>Same as surface="elevated".</p>
45
45
  </rel-container>
46
46
  ```
@@ -55,7 +55,7 @@
55
55
 
56
56
  | Property | Attribute | Type | Default | Description |
57
57
  | --- | --- | --- | --- | --- |
58
- | `padding` | `padding` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Inner padding step: `sm` = spacing-xs, `md` = spacing-md, `lg` = spacing-xl, `xl` = spacing-2xl. Reflected. |
58
+ | `padding` | `padding` | `"small" \| "medium" \| "large" \| "extra-large" \| "sm" \| "md" \| "lg" \| "xl"` | `"medium"` | Inner padding step: `small` = spacing-xs, `medium` = spacing-md, `large` = spacing-lg. `extra-large` = spacing-2xl is a code-only extension — Figma publishes only the three steps above — and is supported, not deprecated. `sm` / `md` / `lg` / `xl` are alternative spellings and stay supported, normalised to the full words. Reflected. |
59
59
  | `shadow` | `shadow` | `boolean` | `false` | Adds the shadow-300 elevation box-shadow to the surface. |
60
60
  | `surface` | `surface` | `"default" \| "elevated" \| "transparent"` | `undefined` | Surface fill: default (page surface) \| elevated (raised surface) \| transparent. Takes precedence over the deprecated `background` prop. Reflected. |
61
61
 
@@ -99,8 +99,8 @@
99
99
 
100
100
  ## Notes / Edge Cases
101
101
 
102
- - Padding steps map to spacing tokens: `sm` = `--rel-spacing-xs`, `md` = `--rel-spacing-md`, `lg` = `--rel-spacing-xl`, `xl` = `--rel-spacing-2xl`.
103
- - Surface fills: `default` = `--rel-bg-surface`, `elevated` = `--rel-bg-surface-elevated`, `transparent` = no fill. The corner radius (`--rel-border-radius-sm`) is applied regardless of surface, and `shadow` adds `--rel-shadows-shadow-300`.
102
+ - Padding steps map to spacing tokens: `small` = `--rel-spacing-xs`, `medium` = `--rel-spacing-md`, `large` = `--rel-spacing-lg`. `extra-large` = `--rel-spacing-2xl` is a code-only extension: Figma's Container publishes only three padding steps, and it is supported rather than deprecated. The short spellings `sm` / `md` / `lg` / `xl` keep working and normalise to the full words.
103
+ - Surface fills: `default` = `--rel-bg-surface`, `elevated` = `--rel-bg-surface-elevated`, `transparent` = no fill. The corner radius (`--rel-border-radius-sm`) is applied regardless of surface, and `shadow` adds `--rel-shadows-md`.
104
104
  - `resolvedSurface` tells you which fill is in effect: an explicit `surface` wins, otherwise the deprecated `background` value is mapped (white → default, light → elevated, none → transparent), otherwise `default`.
105
105
  - The inner `<div>` keeps the legacy class names `bg-white` / `bg-light` / `bg-none` for the three surfaces so existing test selectors keep matching; `::part(base)` and `::part(container)` both target that same element.
106
106
  - Slotted `h1`–`h4` receive the headings-sm role with a bottom margin of `--rel-spacing-lg`; slotted `p` receives the body-md role with zero margin. Wrap other content in your own elements to style it.
@@ -86,7 +86,7 @@ Without `display-format` or `formatDate` the field shows the locale long date (`
86
86
  | `value` | `value` | `string \| null` | `null` | Selected date as an ISO `YYYY-MM-DD` string, or `null` when empty; setting it also moves the popover to that month. Reflected. |
87
87
  | `placeholder` | `placeholder` | `string` | `"Select date"` | Placeholder text shown while the field is empty (visual only; the accessible name comes from `label`, or "Select date" when unset). |
88
88
  | `label` | `label` | `string \| null` | `null` | Visible label rendered above the field and wired to the input via `aria-labelledby`; `null` renders no label. |
89
- | `disabled` | `disabled` | `boolean` | `false` | Disables the field, the icons and the popover; the host dims to 60% opacity. Reflected. |
89
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the field, the icons and the popover; the host dims to 40% opacity. Reflected. |
90
90
  | `required` | `required` | `boolean` | `false` | Appends a `*` to the label and sets `aria-required`; no validation is performed. |
91
91
  | `readonly` | `readonly` | `boolean` | `false` | Makes the input read-only and stops the field, icon, Enter and ArrowDown from opening the popover. Reflected. |
92
92
  | `minDate` | `min-date` | `string \| null` | `null` | Earliest selectable date (ISO `YYYY-MM-DD`); earlier days are disabled and typed dates before it are rejected. Wins over the deprecated `min`. |
@@ -146,6 +146,12 @@ Without `display-format` or `formatDate` the field shows the locale long date (`
146
146
 
147
147
  <!-- readme-gen:end -->
148
148
 
149
+
150
+ ## CSS Custom Properties
151
+
152
+ | Custom property | Default | Description |
153
+ | --- | --- | --- |
154
+ | `--rel-overlay-fallback-z` | `1001` | z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place. |
149
155
  ## Accessibility
150
156
 
151
157
  - The text input is a `role="combobox"` with `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls` pointing at the panel, `aria-required` from `required`, `aria-invalid` when `error` is set or typed text failed to parse, and `aria-describedby` pointing at the helper/error line. Its name is the `label` (via `aria-labelledby`) or the fixed string "Select date" when no label is set; the required `*` is `aria-hidden`.
@@ -191,7 +197,7 @@ Without `display-format` or `formatDate` the field shows the locale long date (`
191
197
  - The years grid shows one 12-year page at a time, anchored so the year in view sits in the seventh cell; the chevrons page by 12. In the months grid the chevrons step a year, and in the days grid a month. A month or year cell is disabled only when *every* day it covers falls outside `min-date`/`max-date`.
192
198
  - The panel always reopens on the days grid, whichever view was showing when it closed.
193
199
  - The field is Figma's TextField instance: `border-radius: xs` (4 px), 12 px inline padding at `medium`, a 4 px label gap and an 18 px suffix icon — the same geometry `rel-textfield` uses, so the two line up in a shared form row. Icon and inline padding step with `size` (16/18/20 px and 8/12/16 px).
194
- - The panel is promoted to the browser top layer (`popover="manual"`), so it renders above `rel-modal` / `rel-drawer` and transformed or `overflow: hidden` ancestors, and it repositions on scroll/resize. It flips above the trigger when there is no room below, stays 8 px inside the viewport, is capped at 600 px tall (scrolls) and never wider than the viewport.
200
+ - The panel is promoted to the browser top layer (`popover="manual"`), so it renders above `rel-modal` / `rel-drawer` and transformed or `overflow: hidden` ancestors. It repositions on resize, and **closes on an outside scroll** (changed in 1.4.9 — it used to track the trigger, which left the panel floating over sticky page furniture once the trigger had scrolled out of view). Scrolling inside the panel does not close it. It flips above the trigger when there is no room below, stays 8 px inside the viewport, is capped at 600 px tall (scrolls) and never wider than the viewport.
195
201
  - Below-field text resolves as `errorMessage ?? typed-entry error ?? helperText` while in error, else `helperText`. A typed-entry error styles the field like `error` without setting the property.
196
202
  - `readonly` and `disabled` both stop the popover from opening (field click, icon, Enter, ArrowDown); `disabled` also blocks all pointer events.
197
203
  - `closeOnSelect` is a boolean attribute: it can only be turned off from JavaScript (`picker.closeOnSelect = false`). It affects grid picks only — typed entry and shortcuts always close the popover.
@@ -63,7 +63,17 @@ A thin separator rule, horizontal or vertical, drawn with the neutral elevated b
63
63
 
64
64
  | Part | Description |
65
65
  | --- | --- |
66
- | `base` | The rendered rule (`role="separator"` div) — restyle thickness, colour or margin here. |
66
+ | `base` | The rendered rule — restyle thickness, colour or margin here. |
67
+
68
+ ## Tokens / CSS Variables
69
+
70
+ ### Component hooks
71
+
72
+ Set these on the host (or an ancestor) to customise the element without touching design tokens.
73
+
74
+ | Custom property | Default | Description |
75
+ | --- | --- | --- |
76
+ | `--rel-divider-spacing` | `var(--rel-spacing-md)` | Cross-axis margin around the rule; default 16px. Figma specifies none, so set it to `0` for strict parity — the default is kept for back-compat and is planned to become `0` in 3.0. |
67
77
 
68
78
  <!-- readme-gen:end -->
69
79
 
@@ -65,7 +65,7 @@
65
65
  | Property | Attribute | Type | Default | Description |
66
66
  | --- | --- | --- | --- | --- |
67
67
  | `size` | `size` | `"sm" \| "md" \| "lg" \| "small" \| "medium" \| "large"` | `"md"` | Key-cap scale (Figma Size): `small` 20px tall / label-sm, `medium` 24px / label-md, `large` 28px / label-lg; 4px radius at every size. `sm` / `md` / `lg` are the legacy short spellings and stay accepted. Reflected. |
68
- | `variant` | `variant` | `"default" \| "muted"` | `"default"` | Fill / text contrast: `default` = elevated-1 background with label text; `muted` = elevated-2 background with placeholder-tone text for secondary shortcuts. Both are flat with a 1px border. Reflected. |
68
+ | `variant` | `variant` | `"default" \| "muted"` | `"default"` | Fill / text contrast: `default` = elevated-1 background with label text; `muted` = elevated-2 background with placeholder-tone text for secondary shortcuts. Both carry a 0.5px stroke and the hard 2px bottom edge Figma draws on the key cap. Reflected. |
69
69
  | `inset` | `inset` | `boolean` | `false` | Pressed look: border-strong stroke, 1px downward nudge and (muted variant) a darker fill. Reflected. |
70
70
 
71
71
  ##### Deprecated (still supported — removal targeted for 3.0)
@@ -133,7 +133,7 @@
133
133
 
134
134
  ## Notes / Edge Cases
135
135
 
136
- - Heights are the Figma frame heights (20 / 24 / 28px) with the 1px border inside them (`box-sizing: border-box`), and the corner radius is 4px at every size. The key cap is flat — fill plus 1px stroke, no shadow — matching the Figma component.
136
+ - Heights are the Figma frame heights (20 / 24 / 28px) with the 0.5px stroke inside them (`box-sizing: border-box`), and the corner radius is 4px at every size. The cap carries a hard 2px bottom edge (a zero-blur drop shadow), so it occupies 2px more vertical space than its nominal height.
137
137
  - `small` / `medium` / `large` and `sm` / `md` / `lg` are interchangeable at the CSS level (`:host([size="md"])` and `:host([size="medium"])` share rules); the default value is `md`.
138
138
  - `inset` swaps the stroke to `--rel-border-strong` and nudges the cap 1px down; in the `muted` variant it also darkens the fill.
139
139
  - A `<span>` slotted into `<rel-kbd>` picks up body-sm-regular type (Figma hint text) instead of the label type.
@@ -39,6 +39,40 @@ A responsive CSS Grid layout component with automatic breakpoint handling. Provi
39
39
  </rel-layout>
40
40
  ```
41
41
 
42
+ <!-- readme-gen:start -->
43
+
44
+ <!-- Generated from rel-layout.ts — do not edit; run readme-generate.mjs -->
45
+
46
+ ## API
47
+
48
+ ### Attributes / Properties
49
+
50
+ | Property | Attribute | Type | Default | Description |
51
+ | --- | --- | --- | --- | --- |
52
+ | `gap` | `gap` | `string` | `"1rem"` | — |
53
+ | `columns` | `columns` | `number` | `4` | — |
54
+ | `responsive` | `responsive` | `boolean` | `true` | Reflected. |
55
+
56
+ ## Events
57
+
58
+ | Event | Detail | Bubbles / composed | Description |
59
+ | --- | --- | --- | --- |
60
+ | — | — | — | This element emits no custom events. |
61
+
62
+ ## Slots
63
+
64
+ | Slot | Description |
65
+ | --- | --- |
66
+ | (default) | Main content. |
67
+
68
+ ## CSS Parts
69
+
70
+ | Part | Description |
71
+ | --- | --- |
72
+ | — | None exposed. |
73
+
74
+ <!-- readme-gen:end -->
75
+
42
76
  ## API
43
77
 
44
78
  ### Attributes/Properties
@@ -84,7 +84,8 @@ actions.items = [...actions.items, { label: "Duplicate" }];
84
84
 
85
85
  | Property | Attribute | Type | Default | Description |
86
86
  | --- | --- | --- | --- | --- |
87
- | `type` | `type` | `"default" \| "checkbox" \| "arrow" \| "select-option" \| "fixed" \| "fit"` | `"default"` | Menu behaviour / width mode (Figma Type). `default` = single-select rows (`rel-select`); `checkbox` = toggling rows with a checkbox (`rel-check`); `select-option` = single-select rows with a radio control; `arrow` = rows whose `submenu` opens a flyout (`rel-submenu-select`); `fixed` / `fit` behave like `default` but size the menu to `--rel-menu-width` (300px) or to its content. |
87
+ | `type` | `type` | `"list" \| "checkbox" \| "select-option" \| "arrow" \| "default" \| "fixed" \| "fit"` | `"list"` | Row shape (Figma ".Menu Item" Type). `list` (default) = plain rows; `checkbox` = toggling rows with a checkbox; `select-option` = single-select rows with a trailing check. `arrow` is a code-only extension with no Figma counterpart: rows whose `submenu` opens a flyout. Because it rides this axis, submenus cannot currently be combined with checkbox or select-option rows — see the README. DEPRECATED values, all still working: `default` is the old spelling of `list`; `fixed` / `fit` were width modes and now set `width` instead, leaving the row shape at `list`. |
88
+ | `width` | `width` | `"fixed" \| "fit"` | `undefined` | Sizing mode (Figma "Menu" Type). Both modes are `--rel-menu-width` (300px) wide — Figma's Fixed and Fit variants are both 300px — and the axis varies the HEIGHT: `fixed` pins the panel to `--rel-menu-height` (500px) and scrolls, `fit` hugs its rows. `fit` previously varied the width instead, which was the wrong dimension. This was also fused into `type`, where choosing a width silently forced plain rows and choosing a row shape silently forced the default width. Reflected. |
88
89
  | `size` | `size` | `"large" \| "medium" \| "small"` | `"large"` | Row size (Figma ".Menu Item" Size, 6326:616): large = 48px rows with body-lg labels (Figma default), medium = 40px / body-md, small = 32px / body-sm. Added in 1.4.7; before that rows were a fixed 36px. Reflected. |
89
90
  | `items` | `items` | `MenuItem[]` | `[]` | The rows to render, in order. Each entry is a `MenuItem` (`label`, optional `active`, `checked`, `disabled`, `submenu`, `open`). The array is copied on assignment, so mutate and reassign it to update the menu. |
90
91
 
@@ -166,6 +167,7 @@ actions.items = [...actions.items, { label: "Duplicate" }];
166
167
 
167
168
  ## Notes / Edge Cases
168
169
 
170
+ - **Known limitation — submenus are mutually exclusive with checkbox and select-option rows.** Figma models row shape and width as two independent axes, which is why they are now two props (`type` and `width`). Submenus, however, are a code-only feature with no Figma counterpart, and they ride the `type` axis as `type="arrow"` — so a checkbox menu with submenus cannot currently be expressed. Tracked for 3.0, where `arrow` would become its own boolean the same way the width values just moved off `type`.
169
171
  - `MenuItem` shape: `{ label: string; active?: boolean; checked?: boolean; disabled?: boolean; submenu?: MenuItem[]; open?: boolean }`. `active` marks the selected row (`default`, `fixed`, `fit`, `select-option`, submenu entries), `checked` the checkbox state, `submenu` / `open` the flyout rows and their initial visibility (`arrow` only).
170
172
  - The menu keeps an internal copy of `items`; user interaction updates that copy, never your array. Read the current state from the event detail (`item`, `checked`, `parent` / `sub`) — the objects in `e.detail` are the updated copies. Reassign `items` to change the rows.
171
173
  - Light DOM: `createRenderRoot()` returns the element, so the menu's CSS is injected as a `<style>` block and page styles can reach it. The parts are plain `part` attributes — target them with `rel-menu [part="list"]` / `rel-menu [part="panel"]`, not `::part()`.
@@ -128,7 +128,7 @@ Items are plain HTML placed inside a `<rel-menubar-menu>`; the menubar recognise
128
128
  | Property | Attribute | Type | Default | Description |
129
129
  | --- | --- | --- | --- | --- |
130
130
  | `bindShortcuts` | `bind-shortcuts` | `boolean` | `false` | Opt-in: actually bind the keys declared via `data-shortcut` on items (e.g. `data-shortcut="Ctrl+Z"`) instead of only displaying them; a "Mod" token means Cmd on Apple platforms and Ctrl elsewhere, and keystrokes that originate in inputs, textareas or contenteditable regions are ignored. |
131
- | `type` | `type` | `"fixed" \| "fit"` | `"fit"` | Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). Added in 1.4.8; the default stays `fit`, which is how the bar has always rendered. Reflected. |
131
+ | `type` | `type` | `"fixed" \| "fit"` | `"fit"` | Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). The default stays `fit`, which is how the bar has always rendered. Reflected. |
132
132
 
133
133
  ### Events
134
134
 
@@ -89,11 +89,11 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
89
89
  | --- | --- | --- | --- | --- |
90
90
  | `value` | `value` | `string` | `""` | The concatenated code (all cells joined). Set it to fill the cells programmatically; it never exceeds `length` characters. Reflected. |
91
91
  | `length` | `length` | `number` | `6` | Total number of cells (characters in the code). |
92
- | `groupSize` | `group-size` | `number` | `3` | Cells per visual group; a separator renders between groups. `0` (or `show-separator` off) renders one ungrouped row. |
92
+ | `groupSize` | `group-size` | `number` | `3` | Cells per visual group; a separator renders between groups. Grouping applies only when `length` divides evenly by this — otherwise the row renders ungrouped, because a ragged final group would put a separator before a lone cell. So the default `length="6"` is 3–3, `length="4"` is one ungrouped row of four, and `group-size="2"` with `length="4"` is 2–2. `0` (or `show-separator` off) also renders one ungrouped row. |
93
93
  | `placeholder` | `placeholder` | `string` | `""` | Placeholder character shown in every empty cell. |
94
94
  | `inputMode` | `input-mode` | `"numeric" \| "alphanumeric" \| ""` | `""` | Accepted character set (Trello 5). Describes what the field ACCEPTS, not a semantic input type — hence the name. Explicitly set it wins over the deprecated `type`; unset (`""`, like the native `inputMode` accessor it shadows on the host), `type` (default `"numeric"`) applies. Note the lowercase `inputmode` prop is a different thing: the raw native keyboard hint forwarded to each cell. |
95
95
  | `mask` | `mask` | `boolean` | `false` | Renders the cells as `type="password"` so entered characters are masked. |
96
- | `disabled` | `disabled` | `boolean` | `false` | Disables every cell and dims the control to 0.6 opacity. Reflected. |
96
+ | `disabled` | `disabled` | `boolean` | `false` | Disables every cell and dims the control to 0.4 opacity (cell fill and border unchanged, as Figma specifies). Reflected. |
97
97
  | `readonly` | `readonly` | `boolean` | `false` | Makes every cell read-only: the value is shown but typing, paste and keyboard editing are ignored. Reflected. |
98
98
  | `required` | `required` | `boolean` | `false` | Marks the first cell `required` for native form validation. |
99
99
  | `name` | `name` | `string \| null` | `null` | When set, renders a hidden `<input name="…">` (part `hidden-input`) carrying `value` for native form submission. |
@@ -102,7 +102,7 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
102
102
  | `pattern` | `pattern` | `string \| null` | `null` | Native `pattern` attribute forwarded to every cell (per-character regex for form validation). |
103
103
  | `ariaLabel` | `aria-label` | `string \| null` | `null` | Accessible name of the whole group; defaults to "One-time password input". Each cell is additionally labelled "Digit/Character N of M". |
104
104
  | `invalid` | `invalid` | `boolean` | `false` | Error styling (danger border on every cell) and `aria-invalid="true"`. Reflected. |
105
- | `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. |
105
+ | `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. Even when on, separators appear only if `length` divides evenly by `group-size`; see `groupSize`. |
106
106
  | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Cell size on the shared field scale (square, border-box): `small` 32px, `medium` 40px, `large` 48px (the Figma OTP Input cell). `sm` / `md` / `lg` are deprecated aliases of the full words. Reflected. |
107
107
 
108
108
  #### Deprecated (still supported — removal targeted for 3.0)
@@ -164,6 +164,7 @@ A one-time-password input implementing the Figma "OTP Input" cell: one single-ch
164
164
  - `input-mode` unset (`""`) falls back to the deprecated `type` prop (default `"numeric"`); set `input-mode` explicitly and it always wins.
165
165
  - The lowercase `inputmode` prop is the raw native keyboard hint, not the accepted character set — normally leave it unset.
166
166
  - `show-separator` is a boolean attribute: `show-separator="false"` still enables separators. Hide them with `el.showSeparator = false` or `group-size="0"`.
167
+ - **Separators require an even split.** Grouping applies only when `length` divides evenly by `group-size`; otherwise the cells render as one ungrouped row. A separator marks a boundary between two equal groups, so a length that does not divide would put one before a lone trailing cell — `length="4"` with the default `group-size="3"` used to draw `3 – 1`. Affected lengths at the default group size are 4, 5, 7 and 8; each now renders ungrouped. To group a length of 4, set `group-size="2"` for `2 – 2`.
167
168
  - `required` is applied to the first cell only, so native form validation blocks submission when the code is empty but not when it is partial — check `e.detail.complete` yourself.
168
169
  - The `autocomplete` prop currently has no effect: every cell always renders `autocomplete="one-time-code"`.
169
170
  - `disabled` and `readonly` also suppress paste and keyboard handling; the value stays visible.
@@ -48,8 +48,8 @@ Page navigation implementing the Figma "Pagination" component in its three **Typ
48
48
  | `totalPages` | `totalpages` | `number` | `0` | Total number of pages. `0` (the default) lets `totalItems` / `perPage` compute the page count instead. Attribute `totalPages`, also read once as `total-pages` on connect. |
49
49
  | `totalItems` | `totalitems` | `number` | `0` | Total item count; with `perPage` it computes the page count when `totalPages` is `0` and is the "of N" figure in the count-text readout. Attribute `totalItems`, also read once as `total-items` on connect. |
50
50
  | `perPage` | `perpage` | `number` | `10` | Items per page, used to compute the page count from `totalItems` and the "start–end" range in the count-text readout. Attribute `perPage`, also read once as `per-page` on connect. |
51
- | `showLabel` | `showlabel` | `boolean` | `false` | Shows a "Viewing page X of Y" caption under the extended layout (ignored by the other types). |
52
- | `disabled` | `disabled` | `boolean` | `false` | Dims the whole control to 50% opacity, shows a `not-allowed` cursor and ignores every page change. Reflected. |
51
+ | `showLabel` | `showlabel` | `boolean` | `false` | DELIBERATE DIVERGENCE — do not flip this default to `true`. Figma's `Type=Extended` has no boolean for the caption: the "Viewing page 1 of 10" text is a fixed child of both Extended variants and is load-bearing for their 78px height (40 + spacing-md + 22). Here it stays opt-in. Ruled by the user: our behaviour stands and the DESIGN FILE follows our docs. Defaulting it on would silently add 38px to every existing extended pagination. |
52
+ | `disabled` | `disabled` | `boolean` | `false` | Dims the whole control to 40% opacity, shows a `not-allowed` cursor and ignores every page change. Reflected. |
53
53
  | `hrefTemplate` | `hreftemplate` | `string` | `""` | Reserved for link-mode pagination. Currently accepted but not rendered: every page control is a `<button>`, so navigate in your `rel-change` handler instead. |
54
54
  | `ariaLabel` | `arialabel` | `string` | `"Pagination"` | Accessible name of the `role="navigation"` landmark (attribute `ariaLabel`). Set it when a page contains more than one pagination control. |
55
55
 
@@ -17,11 +17,11 @@ A click-to-open floating panel anchored to a trigger, implementing the Figma "Po
17
17
  <!-- Panel padding (Figma Size S / M / L = 12 / 16 / 20px) -->
18
18
  <rel-popover>
19
19
  <rel-button variant="secondary">Compact</rel-button>
20
- <rel-popover-content size="sm" aria-label="Compact panel"><p>12px padding</p></rel-popover-content>
20
+ <rel-popover-content size="small" aria-label="Compact panel"><p>12px padding</p></rel-popover-content>
21
21
  </rel-popover>
22
22
  <rel-popover>
23
23
  <rel-button variant="secondary">Roomy</rel-button>
24
- <rel-popover-content size="lg" aria-label="Roomy panel"><p>20px padding</p></rel-popover-content>
24
+ <rel-popover-content size="large" aria-label="Roomy panel"><p>20px padding</p></rel-popover-content>
25
25
  </rel-popover>
26
26
 
27
27
  <!-- Positioning: side, alignment and pixel offsets -->
@@ -143,7 +143,7 @@ Set these on the host (or an ancestor) to customise the element without touching
143
143
  | Custom property | Default | Description |
144
144
  | --- | --- | --- |
145
145
  | `--rel-popover-arrow-bg` | `var(--rel-bg-neutral)` | Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`). |
146
- | `--rel-popover-arrow-border` | `var(--rel-border-default)` | Stroke of the `arrow` chevron's two slanted edges (default `--rel-border-default`). |
146
+ | `--rel-popover-arrow-border` | `transparent` | **Deprecated.** Stroke of the `arrow` chevron's two slanted edges. The panel no longer draws a border — overlapping surfaces are separated by shadow — so this defaults to `transparent` and the chevron has no edge. Set it to restore a stroke. |
147
147
 
148
148
  ### `<rel-popover-content>`
149
149
 
@@ -152,7 +152,7 @@ Set these on the host (or an ancestor) to customise the element without touching
152
152
  | Property | Attribute | Type | Default | Description |
153
153
  | --- | --- | --- | --- | --- |
154
154
  | `ariaLabel` | `aria-label` | `string` | `""` | Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. |
155
- | `size` | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Figma Size axis (S/M/L): panel padding 12/16/20px. Reflected. |
155
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Figma Size axis: panel padding 12px (`small`), 16px (`medium`, default) or 20px (`large`). `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. Reflected. |
156
156
 
157
157
  ### Events
158
158
 
@@ -200,4 +200,4 @@ Set these on the host (or an ancestor) to customise the element without touching
200
200
  - **Arrow.** `arrow` draws a 14×11 chevron on the panel edge facing the trigger, centred on the trigger and clamped 15px in from the panel corners; the trigger gap becomes `max(sideoffset, 13px)`. Colour it with `--rel-popover-arrow-bg` / `--rel-popover-arrow-border` (defaults match `<rel-popover-content>`); if you use a custom panel with a different surface, set both so the chevron blends in.
201
201
  - **`<rel-popover-content>`** is `display: block; width: 100%; max-width: 400px`; override `max-width` on the element for wider panels. Slotted `h1`–`h6` get heading-sm type and `p` gets placeholder-tone body text with a bottom margin (none on the last paragraph).
202
202
  - **`disabled`** blocks the trigger and dims the host but does not close a panel that is already open — set `open = false` first if needed.
203
- - The `size` values on `<rel-popover-content>` are the short spellings `sm` / `md` / `lg`; there are no full-word aliases on this element.
203
+ - The `size` values on `<rel-popover-content>` are `small` / `medium` / `large`. The short spellings `sm` / `md` / `lg` are alternative spellings and keep working — they are normalised to the full words before the attribute reflects, so `rel-popover-content[size="medium"]` matches whichever spelling was written.
@@ -1,6 +1,6 @@
1
1
  # rel-progress
2
2
 
3
- `<rel-progress>` is the Figma "Progress" indicator in its two shapes and two behaviours: a full-width 6px linear bar or a 48px circular ring, each either determinate (fills to `value`, 0–100) or indeterminate (loops an animation while you wait). Use `linear-determinate` / `circular-determinate` for uploads, imports and multi-step flows where progress is known, and the `*-indeterminate` modes as a loading signal while a request is in flight. It is a passive `role="progressbar"` element — it does not block interaction (use `rel-modal` with `loading` for that) and does not announce completion on its own.
3
+ `<rel-progress>` is the Figma "Progress" indicator in its two shapes and two behaviours: a full-width 2px linear bar or a 36px circular ring, each either determinate (fills to `value`, 0–100) or indeterminate (loops an animation while you wait). Use `linear-determinate` / `circular-determinate` for uploads, imports and multi-step flows where progress is known, and the `*-indeterminate` modes as a loading signal while a request is in flight. It is a passive `role="progressbar"` element — it does not block interaction (use `rel-modal` with `loading` for that) and does not announce completion on its own.
4
4
 
5
5
  ## Usage
6
6
 
@@ -45,7 +45,7 @@
45
45
 
46
46
  | Property | Attribute | Type | Default | Description |
47
47
  | --- | --- | --- | --- | --- |
48
- | `mode` | `mode` | `"linear-determinate" \| "linear-indeterminate" \| "circular-determinate" \| "circular-indeterminate"` | `"linear-determinate"` | Shape and behaviour: `linear-*` is a full-width 6px bar, `circular-*` a 48px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. Reflected. |
48
+ | `mode` | `mode` | `"linear-determinate" \| "linear-indeterminate" \| "circular-determinate" \| "circular-indeterminate"` | `"linear-determinate"` | Shape and behaviour: `linear-*` is a full-width 2px bar, `circular-*` a 36px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. Reflected. |
49
49
  | `value` | `value` | `number` | `0` | Completion percentage 0–100 for the determinate modes; out-of-range values are clamped before rendering and before `aria-valuenow`. |
50
50
  | `label` | `label` | `string` | `""` | Accessible name of the progressbar (not rendered visually); defaults to "Progress" for determinate and "Loading" for indeterminate modes. |
51
51
 
@@ -82,7 +82,7 @@
82
82
 
83
83
  - `value` is clamped to 0–100 before rendering and before `aria-valuenow`; the indeterminate modes ignore it entirely.
84
84
  - The `track` and `indicator` parts exist only in the two linear modes. The circular modes render an inline `<svg>` with no parts, so `::part(track)` matches nothing there.
85
- - Linear bars are 6px tall with a square radius and stretch to the host's width (host is `display: block`). Circular rings are fixed at 48px and the host switches to `display: inline-block`, so they sit inline with text.
85
+ - Linear bars are 2px tall with a 4px radius and stretch to the host's width (host is `display: block`). Circular rings are fixed at 36px and the host switches to `display: inline-block`, so they sit inline with text.
86
86
  - The linear determinate width animates over 0.3s and the circular arc over 0.35s; a value that jumps from 0 to 100 therefore takes a moment to catch up visually.
87
87
  - Circular strokes are 3px (determinate) and 4px (indeterminate) with a flat (`butt`) line cap; the determinate ring starts at 12 o'clock via a −90° rotation.
88
88
  - The indeterminate linear bar is a 30%-wide segment that sweeps left to right on a 1.5s loop; the spinner rotates on a 2s loop with a 1.5s dash animation.
@@ -63,7 +63,7 @@ plan.addEventListener("rel-change", (e) => {
63
63
  | `options` | `options` | `RadioOption[]` | `[]` | The options to render, in order. Each entry is a `RadioOption` (`value`, `label`, optional `supportingText`, `disabled`, `icon` or `avatar`). Set as a property (`.options=`) or as a JSON attribute. |
64
64
  | `name` | `name` | `string` | `"radio-group"` | `name` given to every native radio input inside this element. It scopes native radio-group behaviour (arrow-key movement, mutual exclusion) to this group, so give each group on a page a distinct name. |
65
65
  | `orientation` | `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Layout axis of the option list. `"vertical"` (default) stacks options in a column; `"horizontal"` lays them out in a wrapping row. Reflected. |
66
- | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control scale (Figma Size). `"medium"` (default, Figma M) = 20px control with body-lg labels; `"large"` (Figma L) = 24px control with body-xl labels; `"small"` (Figma S) = 20px control with body-md labels — S and M share the control size in the spec, only typography steps down. Reflected. |
66
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Control scale, matching Figma's Size axis. `"small"` and `"medium"` (default) both draw a 20px control — they differ by TYPE, body-md labels against body-lg — and `"large"` steps the control to 24px with body-xl labels. Supporting text sits one body step below the label at every size (body-sm / md / lg, i.e. 12 / 14 / 16px). `sm` / `md` / `lg` are alternative spellings and stay supported — normalised to the full words, not deprecated. Small and Medium sharing a control is Figma's design, not an oversight: verified against the live Published DS file on 2026-09-25, `.Radio Control` measures 20x20 in all 15 Small variants and all 15 Medium ones. v1.4.8 briefly gave Small its own 16px control on the mistaken premise that the ramp was 16/20/24; 1.4.9 reverts that. Reflected. |
67
67
  | `density` | `density` | `"compact" \| "comfortable"` | `"compact"` | Supporting-text layout (Figma Density). `"compact"` (default) puts the supporting text on its own line under the row, indented past the control; `"comfortable"` stacks label + supporting text beside the control. Reflected. |
68
68
  | `alignment` | `alignment` | `"leading" \| "trailing"` | `"leading"` | Control position (Figma Layout). `"leading"` (default) renders the control before the label; `"trailing"` pushes it to the far edge of the row. Trailing has no compact layout in the spec, so it always renders with comfortable density. Meant for vertical orientation — trailing options stretch to the group's full width. Reflected. |
69
69
  | `selectedValue` | `selectedvalue` | `string \| null` | `null` | `value` of the currently selected option, or `null` when nothing is selected. Set it to preselect an option; it is updated on every user selection. Reflected. |
@@ -81,6 +81,7 @@ The left pane lists Today (`T`), Tomorrow (`M`), Yesterday (`Y`), This Week (`W`
81
81
  | `triggerVariant` | `trigger-variant` | `"default" \| "icon"` | `"default"` | `default` renders the text field; `icon` renders a single calendar icon button instead (no typed entry, no clear button, parts `trigger`/`value` absent). |
82
82
  | `clearable` | `clearable` | `boolean` | `false` | Shows a clear (×) icon while a range is set; clearing fires `rel-change` with empty `from`/`to`. Only with the default trigger. |
83
83
  | `fullWidth` | `fullwidth` | `boolean` | `false` | Stretches the host to fill its parent's width (attribute `fullwidth`), mirroring rel-time-picker / rel-date-picker. Off by default so the existing intrinsic-width (`inline-block`) rendering is unchanged. Reflected. |
84
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Trigger height on the shared field scale — `small` 32 / `medium` 40 / `large` 48px border-box — matching rel-textfield, rel-date-picker and rel-time-picker so the four align in a form row. Also ramps the trigger's text and inline padding. The panel is unaffected: Figma specifies one panel size. `sm` / `md` / `lg` are alternative spellings and stay supported — normalised to the full words, not deprecated. Reflected. |
84
85
 
85
86
  ### Public fields / accessors
86
87
 
@@ -112,11 +113,17 @@ The left pane lists Today (`T`), Tomorrow (`M`), Yesterday (`Y`), This Week (`W`
112
113
  | `panel` | The floating panel (`role="dialog"`) holding presets and calendar |
113
114
  | `list` | The preset column and the day grid |
114
115
  | `option` | Each quick-select preset button |
115
- | `action` | The previous-month and next-month buttons |
116
+ | `action` | The header's previous and next buttons; they step a month, a year or a 12-year page depending on which view the panel is showing |
116
117
  | `footer` | The Reset / Cancel / Apply button row |
117
118
 
118
119
  <!-- readme-gen:end -->
119
120
 
121
+
122
+ ## CSS Custom Properties
123
+
124
+ | Custom property | Default | Description |
125
+ | --- | --- | --- |
126
+ | `--rel-overlay-fallback-z` | `1001` | z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place. |
120
127
  ## Accessibility
121
128
 
122
129
  - Default trigger: the input is a `role="combobox"` with `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls` pointing at the panel, `aria-label` = `placeholder`, `aria-invalid` while typed text is in error and `aria-describedby` pointing at the error line. The icon buttons are labelled "Clear date range" and "Open date range panel" (with `aria-expanded`).
@@ -148,13 +155,15 @@ The left pane lists Today (`T`), Tomorrow (`M`), Yesterday (`Y`), This Week (`W`
148
155
 
149
156
  ## Notes / Edge Cases
150
157
 
158
+ - **The header month and year are buttons.** Clicking the month opens a 12-month grid; clicking the year opens a 12-year page that pages back indefinitely with the chevrons, so a date years in the past is a few clicks rather than a few dozen. Picking a year drills to Months, picking a month returns to the day grid. The chevrons step whatever the current view shows — a month, a year or a whole page — and the panel always reopens on the day grid.
159
+ - **Navigating does not select.** The switcher moves the VIEW only: it writes to the month and year on display and never to the draft or the committed range, so Reset, Cancel and Apply behave exactly as they did, and reopening still seeds the view from the committed start date.
151
160
  - Draft vs committed: grid picks and presets only change the draft shown in the From / To readouts. Apply commits it (fires `rel-change`, closes); Cancel, Escape, an outside click, or the popup backdrop discard it; Reset empties the draft. Reset followed by Apply clears the committed range (`rel-change` with empty strings) — Apply is disabled only when both the draft and the committed range are empty.
152
161
  - Second-click ordering: picking a day before the current draft start makes it the new start and the old start the end.
153
162
  - Setting `open = true` from code shows the panel but neither seeds the draft from `from`/`to` nor fires `rel-open-change`; only user interaction does both.
154
163
  - The trigger text, From / To readouts, month names and day labels are always rendered in `en-US` (`MM/DD/YYYY`); `locale` only affects month-name matching in typed input.
155
164
  - Leading and trailing cells of adjacent months are visible but never selectable, and render at 40% label opacity (Figma "Day of a week = Other Month"). Today gets a 1 px brand ring; weekend cells render at 70% opacity (Figma "Day of a week = Weekend") unless they are range endpoints.
156
165
  - Range styling follows Figma ".Date Range Days": the selected run is an `elevated-1` band across the full cell, rounded at the two ends (a one-day range is a single rounded pill), and the two endpoints are `inverse-2` discs with inverse semibold labels. Days between the endpoints keep their normal label on the band.
157
- - The panel (and the popup backdrop) is promoted to the browser top layer (`popover="manual"`), so it works inside `rel-modal` / `rel-drawer` and above transformed or `overflow: hidden` ancestors. In `popover` mode it anchors below the host, flips above when there is no room, stays 8 px inside the viewport and repositions on scroll/resize; in `popup` mode it is fixed-centred behind a 40% black backdrop (part `overlay`) whose click cancels. It never exceeds the viewport and scrolls internally.
166
+ - The panel (and the popup backdrop) is promoted to the browser top layer (`popover="manual"`), so it works inside `rel-modal` / `rel-drawer` and above transformed or `overflow: hidden` ancestors. In `popover` mode it anchors below the host, flips above when there is no room, stays 8 px inside the viewport, repositions on resize and **closes on an outside scroll** (changed in 1.4.9 — it used to track the trigger, leaving the panel over sticky page furniture once the trigger had scrolled away); scrolling inside the panel does not close it. In `popup` mode it is fixed-centred behind a 40% black backdrop (part `overlay`) whose click cancels. It never exceeds the viewport and scrolls internally.
158
167
  - `width`, `min-width`, `max-width`, `height`, `min-height` and `max-height` are copied to the host's inline style on every update, so they override stylesheet rules on the host.
159
168
  - The icon trigger has no typed entry, no clear icon and no error line; parts `trigger` and `value` are not rendered in that variant.
160
169
  - There is no helper-text or external error API; the only text below the field is the typed-entry error.
@@ -68,7 +68,7 @@ The control comes in three sizes that match the text field ramp — `small` 32px
68
68
  { label: 'Oldest', value: 'oldest' },
69
69
  { label: 'Popular', value: 'popular' }
70
70
  ];
71
- sortOrder.addEventListener('changed', (e) => {
71
+ sortOrder.addEventListener('rel-change', (e) => {
72
72
  console.log('Selected:', e.detail.value);
73
73
  });
74
74
  </script>
@@ -96,7 +96,7 @@ const options: SegmentOption[] = [
96
96
 
97
97
  | Property | Attribute | Type | Default | Description |
98
98
  | --- | --- | --- | --- | --- |
99
- | `options` | `options` | `SegmentOption[]` | `[]` | The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. |
99
+ | `options` | `options` | `SegmentOption[]` | `[]` | The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string; badge?: number \| string \| boolean }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. |
100
100
  | `active` | `active` | `string` | `""` | `value` of the currently selected segment. Updated by the component on click / arrow keys; set it to pre-select a segment (an unknown value selects nothing). |
101
101
  | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control height, matching the TextField ramp: `small` (32px — 28px segments, `action-md` 12px text, 16px icons), `medium` (default, 40px — 36px segments, `action-lg` 14px text, 18px icons) or `large` (48px — 44px segments, `action-xl` 16px text, 20px icons). |
102
102
  | `align` | `align` | `"left" \| "middle" \| "right"` | `"left"` | `justify-content` of the group's inline-flex container: `left` (default), `middle` or `right`. Only visible when the host is made wider than its content. |
@@ -106,7 +106,8 @@ const options: SegmentOption[] = [
106
106
 
107
107
  | Event | Detail | Bubbles / composed | Description |
108
108
  | --- | --- | --- | --- |
109
- | `changed` | `{ value: option.value }` | bubbles / composed | Fired after a non-disabled segment is selected by click or arrow / Home / End key (also when it was already the active one). Detail: `{ value: string }`. |
109
+ | `rel-change` | `{ value: option.value }` | bubbles / composed | Fired after a non-disabled segment is selected by click or arrow / Home / End key (also when it was already the active one). Detail: `{ value: string }`. The canonical name — prefer it over the legacy `changed`, which fires alongside it with the same detail. |
110
+ | `changed` | `{ value: option.value }` | bubbles / composed | Legacy alias of `rel-change`, fired with the same detail at the same time. Kept indefinitely so existing listeners keep working; new code should use `rel-change`. |
110
111
 
111
112
  ## Slots
112
113
 
@@ -121,6 +122,7 @@ const options: SegmentOption[] = [
121
122
  | `option` | Each segment's native `<button role="radio">`; the active one carries the `active` class and `aria-checked="true"`. |
122
123
  | `icon` | A segment's optional leading `rel-icon` element. |
123
124
  | `label` | The wrapper around a segment's label text. |
125
+ | `badge` | A segment's optional trailing `rel-badge`; rendered only when the option sets `badge`. |
124
126
  | `base` | The `role="radiogroup"` track that lays out the segments. |
125
127
 
126
128
  <!-- readme-gen:end -->
@@ -130,7 +132,7 @@ const options: SegmentOption[] = [
130
132
  - The container is a `role="radiogroup"`; give it a name with `field-aria-label` — the component renders no visible group label, so without it screen readers announce an unnamed group.
131
133
  - Every segment is a native `<button role="radio">` with `aria-checked="true"` on the active one and `aria-checked="false"` on the rest. Disabled options get the native `disabled` attribute, so they are not focusable and do not fire `click`.
132
134
  - Roving tabindex (APG radio-group pattern): the whole group is a single Tab stop. The active segment carries `tabindex="0"`; if `active` matches no enabled option, the first enabled segment takes the Tab stop instead (without becoming selected). All other segments are `tabindex="-1"`.
133
- - Arrow keys both move focus and select, so they emit `changed` immediately. Focus is moved to the newly active segment after the re-render.
135
+ - Arrow keys both move focus and select, so they emit the change event immediately. Focus is moved to the newly active segment after the re-render.
134
136
  - Focus ring: a 2px `border-focus` outline drawn inside the segment (`outline-offset: -2px`) and raised with `z-index: 1` so it stays above the neighbouring segment.
135
137
  - Nothing is announced beyond the native `aria-checked` change.
136
138
 
@@ -144,7 +146,7 @@ const options: SegmentOption[] = [
144
146
 
145
147
  ## Notes / Edge Cases
146
148
 
147
- - The change event is named `changed` (no `rel-` prefix, unlike most components); it bubbles and is composed, so it can be listened for on any ancestor. It also fires when the already-active segment is clicked again.
149
+ - **Two events fire for one selection — listen to ONE of them or your handler runs twice.** The change event is `rel-change`, matching the rest of the library. The legacy un-prefixed `changed` fires alongside it with the same detail and is kept so existing listeners keep working — listen to one, not both. Both bubble and are composed, so either can be caught on any ancestor, and both fire when the already-active segment is clicked again.
148
150
  - `active` is a plain string compared against `option.value`; setting it to a value that is not in `options` (or to a disabled option's value) renders no active segment.
149
151
  - `options` is compared by reference: mutate-in-place changes are not picked up — assign a new array.
150
152
  - The host is `display: inline-block` and the group `inline-flex`, so it sizes to its content; `align` has no visible effect until the host is stretched (e.g. `style="display: block; width: 100%"`). Every segment has `min-width: 80px`, so a group is never narrower than 80px per segment.
@@ -13,7 +13,7 @@
13
13
  country.addEventListener("changed", (e) => console.log(e.detail.value)); // "Canada"
14
14
  </script>
15
15
 
16
- <!-- Object options: value / label / icon / supportingText (supportingText shows in multi-select rows only) -->
16
+ <!-- Object options: value / label / icon / supportingText (a second line under the label, in single and multi rows) -->
17
17
  <script>
18
18
  country.options = [
19
19
  { value: "air", label: "Air freight", icon: "air" },
@@ -70,7 +70,7 @@
70
70
  | `labelStyle` | `label-style` | `"default" \| "floating" \| "none"` | `"default"` | Label presentation: `default` static label above the trigger; `floating` label rests in the value area and floats onto the border when focused, open or filled; `none` renders no label (supply `fieldAriaLabel` / `fieldAriaLabelledby`). Reflected. |
71
71
  | `fieldAriaLabel` | `field-aria-label` | `string` | `undefined` | Accessible name for the trigger. Required when `labelStyle="none"`, since no `<label>` renders and a placeholder is not a valid naming mechanism. |
72
72
  | `fieldAriaLabelledby` | `field-aria-labelledby` | `string` | `undefined` | IDREF list of external element(s) naming the trigger. Wins over `fieldAriaLabel`. |
73
- | `options` | `options` | `(string \| SelectOptionObject)[]` | `[]` | Options to list. Each entry is a plain string (used as both value and label) or `{ value, label?, supportingText?, icon? }`; `icon` is a `rel-icon` name rendered before the label, `supportingText` shows only in multi-select rows. |
73
+ | `options` | `options` | `(string \| SelectOptionObject)[]` | `[]` | Options to list. Each entry is a plain string (used as both value and label) or `{ value, label?, supportingText?, icon? }`; `icon` is a `rel-icon` name rendered before the label, `supportingText` renders as a second line in both single- and multi-select rows. NOTE — the panel's search matches the LABEL ONLY. `supportingText` is displayed but not searched, so an option is not findable by its second line here. This differs deliberately from `rel-autocomplete`, whose filter matches the supporting line as well: there the second line carries identifying data (a DOT or MC number) that a user types to find the row, whereas a select's is descriptive ("Read-only", "Can edit, not delete") and matching it would return rows whose visible label has nothing to do with the query. |
74
74
  | `multiple` | `multiple` | `boolean` | `false` | Enables multi-select: selected values render as removable chips, options as checkboxes, and a Select-all row appears in the panel. |
75
75
  | `selected` | `selected` | `string` | `""` | Selected option value in single-select mode. Ignored when `multiple` is set. |
76
76
  | `selectedValues` | `selected-values` | `string[]` | `[]` | Selected option values in multi-select mode. Reset to `[]` when `multiple` is turned off. |
@@ -85,7 +85,7 @@
85
85
  | `searchPlaceholder` | `search-placeholder` | `string` | `"Search option"` | Placeholder of the in-panel search field (the field is hidden when `noSearch` is set). |
86
86
  | `selectAllLabel` | `select-all-label` | `string` | `"Select all"` | Label of the Select-all row shown at the top of the panel in multi-select mode. |
87
87
  | `maxVisibleChips` | `max-visible-chips` | `number` | `Infinity` | Optional hard cap on visible chips. By default (no cap) the visible count adapts to the trigger's width: chips collapse into the "+N" badge one by one instead of wrapping to a second line. |
88
- | `noSearch` | `no-search` | `boolean` | `false` | Hides the in-panel search field. Figma's "Single Option" dropdown has no search bar at all — use this for short, non-searchable lists. |
88
+ | `noSearch` | `no-search` | `boolean` | `false` | DELIBERATE DIVERGENCE — do not default this to `!multiple`. Figma's `.Menu Dropdown` shows the search row only on `Type=Multioption`; its `Type=Single Option` panels contain five Select Option items and nothing else. Here the search row renders for single-select too unless this is set. Ruled by the user: our behaviour stands and the DESIGN FILE follows our docs. A long single-select list is unusable without search, and making the default depend on `multiple` would change the panel of every existing single-select. |
89
89
 
90
90
  ## Events
91
91
 
@@ -111,6 +111,8 @@
111
111
  | `list` | Alias of `panel` |
112
112
  | `input` | The search input inside the panel |
113
113
  | `option` | A single-select option row |
114
+ | `option-label` | A single-select row's first line (the option label) |
115
+ | `option-supporting-text` | A single-select row's optional second line; rendered only when the option supplies `supportingText` |
114
116
 
115
117
  ## Tokens / CSS Variables
116
118
 
@@ -120,9 +122,11 @@ Set these on the host (or an ancestor) to customise the element without touching
120
122
 
121
123
  | Custom property | Default | Description |
122
124
  | --- | --- | --- |
123
- | `--rel-field-label-reserve` | `calc(var(--rel-body-md-regular-line-height) + var(--rel-spacing-2xs))` | Height held for the absent label row under `labelStyle="none"` so the trigger bottom-aligns with labelled siblings; defaults to the label line-height plus the wrapper gap. Set to `0` to collapse the row entirely (toolbars, table cells). |
124
- | `--rel-select-bg` | `var(--rel-bg-neutral)` | Background of the trigger, the floating label, the panel and its sticky header |
125
+ | `--rel-field-label-reserve` | `calc(var(--rel-body-md-regular-line-height) + var(--rel-spacing-2xs))` | Height held for the label row whenever no label renders above the trigger — `labelStyle="none"`, and `labelStyle="floating"` where the label sits inside the trigger — so the trigger still lines up with labelled siblings in a form row. Defaults to that size's label line-height plus the wrapper gap (24px at `small`, 26px at `medium` and `large`). Set to `0` to collapse the row entirely (toolbars, table cells). |
126
+ | `--rel-select-bg` | `var(--rel-bg-neutral)` | Background of the trigger, the panel and its sticky header. No longer paints behind the floating label — see `--rel-select-surface` |
125
127
  | `--rel-select-bg-disabled` | `var(--rel-bg-neutral-elevated-2)` | Trigger background when `state="disabled"` |
128
+ | `--rel-select-surface` | `var(--rel-select-bg)` | Colour of the strip the floating label lays over the trigger's top border and focus ring, so the label appears to sit in a notch. It overshoots the band by 2px on the page side, so it must match the surface the field sits on. Set it when the field sits on a surface that is not the trigger's own fill, otherwise the strip shows as a short dash of the wrong colour. Mirrors `--rel-textfield-surface` |
129
+ | `--rel-select-surface-disabled` | `var(--rel-select-bg-disabled)` | The same strip while `state="disabled"`, where the trigger fill changes |
126
130
  | `--rel-select-border-default` | `var(--rel-border-default)` | Trigger border colour at rest |
127
131
  | `--rel-select-border-disabled` | `var(--rel-border-accent)` | Trigger border colour when `state="disabled"` |
128
132
  | `--rel-select-border-error` | `var(--rel-border-danger)` | Trigger border colour when `state="error"` |
@@ -141,6 +145,7 @@ Set these on the host (or an ancestor) to customise the element without touching
141
145
  | `--rel-select-search-border` | `var(--rel-border-default)` | Border colour of the search field inside the panel |
142
146
  | `--rel-select-supporting-color` | `var(--rel-text-body)` | Supporting text colour (non-error) |
143
147
  | `--rel-select-value-color` | `var(--rel-text-default)` | Colour of the selected value, the search text, option labels and the check mark |
148
+ | `--rel-overlay-fallback-z` | `1001` | z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place. |
144
149
 
145
150
  <!-- readme-gen:end -->
146
151
 
@@ -174,7 +179,7 @@ Set these on the host (or an ancestor) to customise the element without touching
174
179
  - `state="error"` clears itself to `"default"` on the next selection, toggle, Select-all or chip removal. `state="disabled"` dims the whole field to 50% opacity, makes chips non-removable and blocks pointer events on them.
175
180
  - Turning `multiple` off resets `selectedValues` to `[]`. `selected` is ignored in multi mode and `selectedValues` in single mode.
176
181
  - Chips never wrap: the number shown adapts to the trigger width (measured through a hidden copy of every chip and a `ResizeObserver`), with the remainder collapsed into a trailing "+N" chip. At least one chip always stays visible; `max-visible-chips` is an extra hard cap. A selected value with no matching option is shown as its raw value.
177
- - Option `icon` renders in both single and multi rows; `supportingText` only in multi rows. An unknown icon name logs a `rel-icon` warning and renders nothing (no reserved space).
182
+ - Option `icon` and `supportingText` render in both single and multi rows. Both lines truncate to one line with an ellipsis in either mode (nothing wraps or hyphenates), and a row that carries a second line gets a native `title` with its full text so hovering reveals what was cut. An unknown icon name logs a `rel-icon` warning and renders nothing (no reserved space).
178
183
  - A custom `right-icon` replaces the chevron and does not rotate when the panel opens; the default chevron rotates 180°.
179
184
  - The search field uses the small field sizing for both `small` and `medium` selects; only `large` bumps it to medium. Empty results show "No matches found" (or "No options available" when `options` is empty).
180
185
  - The `helper` part exists only while `supporting-text` is non-empty.
@@ -24,12 +24,12 @@
24
24
  </script>
25
25
 
26
26
  <!-- Centred dialog, small width -->
27
- <rel-modal side="center" size="sm" title="Session expired" description="Sign in again to continue.">
27
+ <rel-modal side="center" size="small" title="Session expired" description="Sign in again to continue.">
28
28
  <rel-button slot="footer" variant="primary">Sign in</rel-button>
29
29
  </rel-modal>
30
30
 
31
31
  <!-- Top-anchored sheet with extra header content -->
32
- <rel-modal side="top" size="lg" title="New release">
32
+ <rel-modal side="top" size="large" title="New release">
33
33
  <rel-tab slot="header" size="small">
34
34
  <rel-tab-item slot="items" label="Summary"></rel-tab-item>
35
35
  <rel-tab-item slot="items" label="Details"></rel-tab-item>
@@ -86,7 +86,7 @@
86
86
  | `subtitle` | `subtitle` | `string` | `""` | Alias for `description`; rendered only when `description` is empty. |
87
87
  | `dismissible` | `dismissible` | `boolean` | `true` | Coarse dismissal switch. When false the close button is hidden and backdrop clicks / Escape are ignored regardless of the granular `close-on-*` / `show-close-button` props. |
88
88
  | `modal` | `modal` | `boolean` | `true` | Modal mode: renders the scrim, sets `role="dialog"` + `aria-modal`, traps Tab inside the panel and moves focus in on open. When false the panel is a `region` with no scrim and no focus trap. |
89
- | `size` | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "fullscreen"` | `"md"` | Panel width scale. `md` matches the Figma Modal Sheet width; `fullscreen` covers the viewport. Reflected. |
89
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "extra-large" \| "fullscreen" \| "sm" \| "md" \| "lg" \| "xl"` | `"medium"` | Panel width scale. `medium` (default) matches the Figma Modal Sheet width; `fullscreen` covers the viewport. `sm` / `md` / `lg` / `xl` are alternative spellings of `small` / `medium` / `large` / `extra-large` and stay supported — they are normalised to the full words, not deprecated. Reflected. |
90
90
  | `variant` | `variant` | `"default" \| "text"` | `"default"` | Body treatment — see `ModalVariant`. `"text"` styles the default slot as the Figma "Text" message slot; the header (and its divider) render as usual above it. Default rendering is unchanged. Reflected. |
91
91
  | `closeOnBackdropClick` | `close-on-backdrop-click` | `boolean` | `true` | Close when the scrim is clicked. Only effective while `dismissible` is true; ignored while `disabled` or `loading`. |
92
92
  | `closeOnEscape` | `close-on-escape` | `boolean` | `true` | Close on the Escape key. Only effective while `dismissible` is true; ignored while `disabled`. |