@reliability-design/web 1.4.7 → 1.4.8

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 (46) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/dist/components/rel-accordion/README.md +2 -1
  3. package/dist/components/rel-autocomplete/README.md +21 -5
  4. package/dist/components/rel-avatar/README.md +9 -7
  5. package/dist/components/rel-badge/README.md +4 -5
  6. package/dist/components/rel-button/README.md +32 -2
  7. package/dist/components/rel-card/README.md +3 -3
  8. package/dist/components/rel-carousel/README.md +2 -0
  9. package/dist/components/rel-checkbox/README.md +7 -4
  10. package/dist/components/rel-chip/README.md +13 -7
  11. package/dist/components/rel-container/README.md +8 -8
  12. package/dist/components/rel-date-picker/README.md +1 -1
  13. package/dist/components/rel-divider/README.md +11 -1
  14. package/dist/components/rel-kbd/README.md +2 -2
  15. package/dist/components/rel-layout/README.md +34 -0
  16. package/dist/components/rel-menu/README.md +3 -1
  17. package/dist/components/rel-menubar/README.md +1 -1
  18. package/dist/components/rel-otp/README.md +4 -3
  19. package/dist/components/rel-pagination/README.md +2 -2
  20. package/dist/components/rel-popover/README.md +5 -5
  21. package/dist/components/rel-progress/README.md +3 -3
  22. package/dist/components/rel-radio/README.md +1 -1
  23. package/dist/components/rel-range-picker/README.md +4 -1
  24. package/dist/components/rel-segmented-button/README.md +2 -1
  25. package/dist/components/rel-select/README.md +5 -3
  26. package/dist/components/rel-sheet/README.md +3 -3
  27. package/dist/components/rel-skeleton/README.md +5 -4
  28. package/dist/components/rel-slider/README.md +3 -2
  29. package/dist/components/rel-snackbar/README.md +13 -7
  30. package/dist/components/rel-stepper/README.md +11 -7
  31. package/dist/components/rel-switch/README.md +16 -7
  32. package/dist/components/rel-tab/README.md +4 -2
  33. package/dist/components/rel-textfield/README.md +18 -1
  34. package/dist/components/rel-toast/README.md +14 -6
  35. package/dist/components/rel-tooltip/README.md +13 -2
  36. package/dist/components/rel-upload/README.md +2 -2
  37. package/dist/index.d.mts +709 -237
  38. package/dist/index.d.ts +709 -237
  39. package/dist/index.js +10397 -7840
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +7458 -4901
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/styles/focus-ring.ts +84 -0
  44. package/dist/styles/vars.css +75 -60
  45. package/dist/{vars-JHTUGNRG.css → vars-DXX6QOAG.css} +75 -60
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,11 +1,46 @@
1
1
  # Changelog — @reliability-design/web
2
2
 
3
- ## 1.4.7 (2026-09-05)
3
+ ## 1.4.8 (2026-09-21)
4
+
5
+
6
+ Consumer-ticket release. Twelve tickets from the testing and implementation team, built development-side and documented from source; the design file is being updated to follow, not the other way round. **Nothing is deprecated in this release** — every new spelling is an *alternative* that normalises, with no console notice. Four changes are visible without a rename to warn you; they are listed under [Changed](#changed-148) and on the docs site at `/docs/migration#v148-visual`.
7
+
8
+ Paired releases: `@reliability-design/angular` 1.4.8, which now requires web `^1.4.8` rather than `^1.4.1`. `@reliability-design/icons` is unchanged and stays on 1.4.4.
9
+
10
+ ### Added
11
+
12
+ - **`rel-switch` text labels and a size axis.** `label` plus a default slot for rich content, `label-placement` (`end` default | `start`), and `size` (`small | medium | large`, with `sm`/`md`/`lg` accepted). The root element is now the `<label>` that owns the input, so clicking the text toggles natively. **`line` and `pill` ramp differently on purpose** — 16/20/24 against 20/24/28 — because Line's height *is* the handle while Pill adds a 2px inset each side. `medium` is unchanged, so no existing switch moves. There is no breakpoint logic: the ramp ships, choosing a size per breakpoint is the application's job.
13
+ - **`rel-toast` positions, sizes and stacking.** `position` takes `top-left | top-center | top-right | bottom-left | bottom-center | bottom-right`, defaulting to today's `bottom-center`; top-anchored toasts now animate downward instead of upward out of their own anchor. `size` is `small | medium | large` with `medium` unchanged. Multiple toasts at one position **stack** instead of overlapping: newest against the anchor edge, `max-visible` (default 3), and overflow **queues rather than drops** — so the `duration` countdown starts when a toast becomes *visible*, not when `show` is set. `max-visible` is read from the first toast to arrive at a position.
14
+ - **`rel-tooltip` `tone`.** `inverse` (default, pixel-identical to 1.4.7) and `neutral`, a light-surface bubble with a 1px border — and the tail carries the border too.
15
+ - **Second lines in suggestion rows.** A `supportingText` key on an object item renders a smaller second line under the label, on `rel-autocomplete` (new) and `rel-select` (see Changed). On **rel-autocomplete** it is **matched by the filter and highlighted**, not merely displayed, so a record can be found by its subtitle — rename the key with `supporting-field`. On **rel-select** the second line is displayed but NOT searched: the panel's search matches the label only. The two differ deliberately, because an autocomplete's second line carries identifying data a user types to find the row while a select's is descriptive. New parts `option-label` and `option-supporting-text` — **single-select rows only**; under `multiple` the row is a nested `rel-checkbox` and the second line has no part path at all.
16
+ - **`rel-autocomplete` multi-select.** `multiple` with `selected-values`, a Select-all row and checkbox rows, mirroring `rel-select`. One deliberate difference: the committed chips sit **below** the field rather than inside it, because a select's trigger is a button that displays a value while an autocomplete's is an editable input the caret lives in.
17
+ - **`rel-range-picker` month and year navigation.** The header month label is now a period switcher with Months and Years views and an unbounded year pager, ported from `rel-date-picker`. Reaching January 2023 is 3 clicks instead of roughly 44.
18
+ - **Full-word size values** on `rel-avatar`, `rel-container` (`padding`), `rel-popover-content` and `rel-modal`, joining badge, chip and slider. `sm` / `md` / `lg` / `xs` / `xl` remain accepted, normalised to the full words. **Not deprecations** — no console notice.
19
+ - **`--rel-shadow-color`** (`rgba(11,11,11,0.10)`), the shadow colour on its own. `filter: drop-shadow()` takes a bare `<color>` and cannot consume a box-shadow-shaped token, so components needing it had no semantic token to bind.
20
+
21
+ ### Fixed
22
+
23
+ - **`rel-radio` sizes rendered identically.** `sm` / `md` / `lg` matched no rule and fell through to the base scale — which *is* medium — so three differently-spelled groups were byte-identical. They now normalise. Separately, `small` gets its own 16px control (see Changed).
24
+ - **Fields did not line up in a form row.** `label-style="floating"` put its control 26px above a labelled sibling, because it reserved no label row. All three label styles now share one baseline. Also fixed: the floating host was consistently 4px *shorter* than the control inside it, and `label-style="none"` ran 2px tall at `small` because its reserve was hard-bound to the medium label role. Affects `rel-textfield` and `rel-select`; `rel-autocomplete` inherits through composition.
25
+ - **`rel-otp` drew a hyphen before a lone cell.** With the default grouping of 3, a 4-character code rendered `[][][]-[]`. Separators now render only when the length divides evenly by the group size, which also fixes lengths 5, 7 and 8.
26
+ - **The calendars' Months and Years grids could lose every tab stop.** Cells carried `tabindex="0"` only when selected, and the year pager moves the page without moving the selected year — so paging back once left all twelve cells at `-1` and the grid unreachable by keyboard. Both `rel-date-picker` and `rel-range-picker`; the day grids were already correct. Arrow, Home/End and Escape navigation added to the range picker's period grids to match the date picker's, and the tab stop now follows focus in both.
27
+ - **`rel-slider`** documented a Medium size that the docs never showed. The component was always correct.
28
+
29
+ ### Changed
30
+
31
+ <a id="changed-148"></a>Four changes are visible with no rename to warn you:
32
+
33
+ - **`rel-radio` `small` shrinks.** Its control was 20px, identical to `medium`; it is now 16px, so the ramp reads 16/20/24. This is a deliberate divergence from the design file, which pins Small and Medium to the same control — a size axis where two of three sizes look identical is a defective API. 16px is `rel-checkbox`'s Small, its sibling selection control.
34
+ - **`rel-select` option `supportingText` now renders in single-select rows.** It previously rendered only in multi-select rows, so a single-select built from options that already carried the key gains a second line it did not have. Rows are then two lines, which makes 32/40/48px the *one-line* height rather than the row height.
35
+ - **Floating-label fields are taller**, because they now reserve the label row so they line up with labelled siblings. `--rel-field-label-reserve: 0` collapses it for toolbars and table cells.
36
+ - **Toasts stack.** Five toasts at one position used to paint on top of each other; now three show and two wait. An app-level toast queue built to work around the old behaviour will still work, but the queueing it exists to provide is now built in.
37
+
38
+ ## 1.4.7 (2026-09-12)
4
39
 
5
40
 
6
41
  Designer-audit release. Every rename is **additive** — the new prop or value is canonical, the old one keeps working and is mapped internally, and its first use logs **one** `console.warn` per component per page (never one per instance). Two breaking removals: `rel-topbar` and `rel-navbar` are gone. The full old → new table is under [Migrating](#migrating-to-147) below and on the docs site at `/docs/migration`.
7
42
 
8
- Paired releases: `@reliability-design/angular` 1.4.7; `@reliability-design/icons` 1.4.5.
43
+ Paired releases: `@reliability-design/angular` 1.4.7. `@reliability-design/icons` is unchanged in this release and stays on 1.4.4 — icons keep their own version counter.
9
44
 
10
45
  ### Added
11
46
 
@@ -76,8 +76,9 @@ accordion.open = true;
76
76
  | `title` | `title` | `string` | `"Accordion"` | Header text. Note it is also the native HTML `title` attribute, so browsers show it as a tooltip on hover. |
77
77
  | `open` | `open` | `boolean` | `false` | Whether the body is expanded; toggled by the header and reported via `open-change`. Setting it from code does not fire the event. Reflected. |
78
78
  | `size` | `size` | `"md" \| "sm" \| "large" \| "medium" \| "small"` | `"md"` | Header scale (Figma "Size"): `large` = 56px header / heading-md title, `medium` = 48px / heading-sm, `small` = 44px / heading-xs (new in 1.4.7). The historical spellings stay supported as aliases: `md` = large (the default), `sm` = medium. Reflected. |
79
- | `type` | `type` | `"default" \| "action"` | `"default"` | Header layout (Figma "Type"): `default` = title + trailing rotating chevron; `action` = leading icon slot (rotating chevron by default) + static trailing arrow-expansion icon, with the body indented under the title. Reflected. |
79
+ | `type` | `type` | `"default" \| "action"` | `"default"` | Header layout (Figma "Type"): `default` = title + trailing rotating chevron; `action` = leading icon slot (rotating chevron by default) + static trailing arrow-expansion icon, with the body indented under the title. DELIBERATE DIVERGENCE — do not "fix" this to a button. For `Type=Action` Figma draws the trailing affordance as a full ghost icon Button instance (Size=Medium 40x40, Size=Small 32x32, Style=Ghost, Type=Icon Only); the code keeps a bare icon. Ruled by the user: our behaviour stands and the DESIGN FILE follows our docs here, not the other way round. The whole header is the click target, so a nested button would add a second focusable control inside it and two competing hit areas. Reflected. |
80
80
  | `appearance` | `appearance` | `"shadow" \| "plain"` | `"shadow"` | Figma "Style" axis — named appearance because `style` collides with the HTML style attribute. `shadow` = 4px-radius card with the shadow-sm effect and no border; `plain` = square corners with a 1px border-subtle stroke and no elevation. Reflected. |
81
+ | `disabled` | `disabled` | `boolean` | `false` | Figma "State=Disabled": dims the whole component to 40% and blocks toggling by pointer and keyboard. The header keeps its fill and stroke — the dulling is opacity alone, as Figma specifies. Reflected. |
81
82
 
82
83
  ## Events
83
84
 
@@ -67,22 +67,26 @@
67
67
 
68
68
  | Property | Attribute | Type | Default | Description |
69
69
  | --- | --- | --- | --- | --- |
70
- | `items` | `items` | `(string \| Record<string, unknown>)[]` | `[]` | Local (or fallback initial) suggestion list. Ignored while searchFn drives results. Object items read two keys directly whatever `display-field` / `value-field` are set to: `icon` (a `rel-icon` name shown before the label) and `disabled` (`true` dulls the row and makes it unselectable by click or keyboard). |
70
+ | `items` | `items` | `(string \| Record<string, unknown>)[]` | `[]` | Local (or fallback initial) suggestion list. Ignored while searchFn drives results. Object items are read through `displayField` / `valueField` plus the fixed keys `icon`, `disabled` and `supportingText` (the last renamable via `supporting-field`); `supportingText` renders as a second line in the row and is matched by the filter alongside the label. |
71
71
  | `value` | `value` | `string` | `""` | Committed value — the selected item's valueField string ("" = none). |
72
72
  | `displayField` | `display-field` | `string` | `"label"` | Object key read for the row label when items are objects. |
73
73
  | `valueField` | `value-field` | `string` | `"value"` | Object key read for the committed value when items are objects. |
74
+ | `supportingField` | `supporting-field` | `string` | `"supportingText"` | Object key read for the row's optional second line. Defaults to `supportingText`, the fixed key `rel-select` and `rel-radio` already use; point it elsewhere when your records cannot be reshaped. |
74
75
  | `filter` | `filter` | `"contains" \| "starts-with" \| "none"` | `"contains"` | Local filtering mode. Ignored when searchFn is set (results arrive pre-filtered). |
75
76
  | `searchFn` | — | `(query: string) => Promise<AutocompleteItem[]>` | `undefined` | Async data source. When set, local filtering is bypassed entirely. Property only. |
76
77
  | `debounceMs` | `debounce-ms` | `number` | `300` | Debounce for searchFn calls, in ms. |
77
78
  | `minSearchLength` | `min-search-length` | `number` | `1` | Minimum typed characters before the panel opens / searchFn fires. 0 = open on focus. |
78
79
  | `maxSuggestions` | `max-suggestions` | `number` | `Infinity` | Optional cap on rendered suggestions (panel scrolls regardless). |
80
+ | `multiple` | `multiple` | `boolean` | `false` | Multi-select mode: rows become checkboxes with a Select-all row, and the committed values live in `selectedValues` rather than `value`. Mirrors `rel-select`'s `multiple` rather than introducing a second pattern. THE TEXT INPUT STAYS A SEARCH BOX. In single mode the input shows the selected label, which is the point of a type-ahead; in multi mode there is no single label to show, and writing a value list into the field would destroy the query the user is typing. `rel-select` can put chips inside its trigger because a trigger is not an editable surface — an input is. So the chips render beneath the field, the query clears after each toggle so the next search starts fresh, and the panel stays open. Reflected. |
81
+ | `selectedValues` | `selected-values` | `string[]` | `[]` | Committed values in `multiple` mode (ignored otherwise). Mirrors `rel-select.selectedValues`. |
82
+ | `selectAllLabel` | `select-all-label` | `string` | `"Select all"` | Label of the Select-all row shown at the top of the panel in `multiple` mode. |
79
83
  | `clearable` | `clearable` | `boolean` | `false` | Shows a trailing ✕ that clears the text and the committed value. |
80
84
  | `loading` | `loading` | `boolean` | `false` | Externally-controlled loading state (internal searchFn loading is automatic). |
81
85
  | `noResultsText` | `no-results-text` | `string` | `"No results found"` | Row shown when a query yields nothing. |
82
86
  | `loadingText` | `loading-text` | `string` | `"Loading…"` | Text shown next to the spinner while suggestions load. |
83
87
  | `label` | `label` | `string` | `undefined` | Visible label, forwarded to the inner `rel-textfield`. Also names the suggestion panel. |
84
88
  | `placeholder` | `placeholder` | `string` | `""` | Placeholder of the input, forwarded to the inner `rel-textfield`. |
85
- | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Field height — `small` 32px, `medium` 40px, `large` 48px — forwarded to the inner `rel-textfield`. |
89
+ | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Field height — `small` 32px, `medium` 40px, `large` 48px — forwarded to the inner `rel-textfield`. Also drives the suggestion panel's row ramp. Reflected. |
86
90
  | `variant` | `variant` | `"default" \| "underline" \| "legend"` | `"default"` | Field chrome (`default` outlined, `underline`, `legend` notched outline), forwarded to the inner `rel-textfield`. |
87
91
  | `labelStyle` | `label-style` | `"default" \| "floating" \| "none"` | `undefined` | Label presentation (`default` / `floating` / `none`), forwarded to the inner field. Left `undefined`, the deprecated `floatingLabel` decides. Reflected. |
88
92
  | `fieldAriaLabel` | `field-aria-label` | `string` | `undefined` | Accessible name for the input. Required when `labelStyle="none"`. |
@@ -113,7 +117,7 @@
113
117
 
114
118
  | Event | Detail | Bubbles / composed | Description |
115
119
  | --- | --- | --- | --- |
116
- | `changed` | `{ value: this.value, item: item ? item.item : null }` | bubbles / composed | Fired when the committed value changes — an item was selected (click, Enter, or an exact text match on Enter / Tab / blur) or the field was cleared. Detail: `{ value, item }`, where `item` is the original string or object, or `null` after a clear. |
120
+ | `changed` | `{ value: this.selectedValues, item: null } / { value: this.value, item: item ? item.item : null }` | bubbles / composed | Fired when the committed value changes — an item was selected (click, Enter, or an exact text match on Enter / Tab / blur) or the field was cleared. Detail: `{ value, item }`, where `item` is the original string or object, or `null` after a clear. |
117
121
  | `rel-change` | `inputText / ""` | bubbles / composed | Fired on every keystroke with the raw input text as the detail (and with `""` after a clear). The committed `value` does not change until an item is selected. |
118
122
  | `rel-clear` | — | bubbles / composed | Fired when the clear icon empties a non-empty field (followed by `changed` and `rel-change`). No detail. |
119
123
  | `rel-search-error` | `{ query, error: err }` | bubbles / composed | Fired when `searchFn` rejects; the panel then shows the no-results row. Detail: `{ query, error }`. |
@@ -128,9 +132,14 @@
128
132
 
129
133
  | Part | Description |
130
134
  | --- | --- |
135
+ | `chips` | — |
136
+ | `chip` | — |
131
137
  | `panel` | The suggestion listbox container (same node as `list`) |
132
138
  | `list` | Alias of `panel` |
133
- | `option` | A suggestion row |
139
+ | `select-all` | — |
140
+ | `option` | A suggestion row. Emitted in BOTH single and `multiple` mode, unlike the two parts below |
141
+ | `option-label` | The row's first line (the matched label). SINGLE-SELECT ROWS ONLY |
142
+ | `option-supporting-text` | The row's optional second line, rendered only when the item supplies one. SINGLE-SELECT ROWS ONLY — under `multiple` the row is a nested `rel-checkbox` and the second line is that component's own `helper` part, inside its shadow root with nothing forwarding it out, so it cannot be reached with `::part()` at all. Style both modes with the `--rel-*` tokens instead |
134
143
  | `label` | The inner field's label (re-exported from `rel-textfield`) |
135
144
  | `control` | The inner field's bordered control box (re-exported from `rel-textfield`) |
136
145
  | `input` | The inner native input (re-exported from `rel-textfield`) |
@@ -146,14 +155,16 @@ Set these on the host (or an ancestor) to customise the element without touching
146
155
  | Custom property | Default | Description |
147
156
  | --- | --- | --- |
148
157
  | `--rel-autocomplete-bg` | `var(--rel-bg-neutral)` | Panel and row background |
149
- | `--rel-autocomplete-divider` | `var(--rel-border-subtle)` | Hairline between rows |
158
+ | `--rel-autocomplete-divider` | `transparent` | Hairline between rows |
150
159
  | `--rel-autocomplete-icon-color` | `var(--rel-text-default)` | Colour of a row's leading `icon` |
151
160
  | `--rel-autocomplete-menu-border` | `var(--rel-border-default)` | Panel border colour. The panel is borderless by design and only draws this on dark surfaces, where its shadow alone can't separate it from the page. |
161
+ | `--rel-autocomplete-menu-max-height` | `360px` | Scroll ceiling of the suggestion panel; default `360px`. Code-owned rather than Figma-derived — every Figma Menu variant hugs its content with no ceiling, which is not shippable for an unbounded result list. |
152
162
  | `--rel-autocomplete-menu-radius` | `var(--rel-border-radius-xs)` | Panel corner radius |
153
163
  | `--rel-autocomplete-menu-shadow` | `var(--rel-shadows-md)` | Panel box-shadow |
154
164
  | `--rel-autocomplete-option-hover` | `var(--rel-action-overlay-hover)` | Background of the hovered / keyboard-highlighted / selected row |
155
165
  | `--rel-autocomplete-option-pressed` | `var(--rel-action-overlay-pressed)` | Background of a row while it is being pressed |
156
166
  | `--rel-autocomplete-status-color` | `var(--rel-text-body)` | Colour of the loading / no-results status rows |
167
+ | `--rel-autocomplete-supporting-color` | `var(--rel-text-caption)` | — |
157
168
  | `--rel-autocomplete-value-color` | `var(--rel-text-default)` | Suggestion label colour |
158
169
 
159
170
  <!-- readme-gen:end -->
@@ -180,6 +191,9 @@ Focus: `focus()` on the host delegates to the inner field. Focusing the field wi
180
191
 
181
192
  ## Notes / Edge Cases
182
193
 
194
+ - **The second line is searchable, and that changes which rows appear.** `supportingText` is matched by the filter alongside the label, because the identifiers it usually carries (a DOT or MC number, a code) are the reason to show it at all. An item whose label does not match but whose supporting text does will now appear in the list, and the matched characters are emboldened on whichever line they were found. Set `filter="none"` or drop `supportingText` if you need the previous label-only behaviour.
195
+ - The second line truncates with an ellipsis rather than wrapping — the panel is pinned to the field's width, and a suggestion row is a fixed-height scanning target. Rows grow from 32 / 40 / 48 to roughly 46 / 58 / 70 when an item supplies one; a row without `supportingText` is unchanged.
196
+ - **In `multiple` mode the text input stays a search box.** It never displays the committed values: in single mode the input shows the selected label, but in multi there is no single label to show, and writing a value list into the field would overwrite the query being typed. The chips therefore render *beneath* the field (`rel-select` can carry them inside its trigger because a trigger is not editable), the query clears after each toggle so the next search starts fresh, and the panel stays open. Read the selection from `selectedValues`, not `value`; `changed` carries the array.
183
197
  - `value` and the visible text are separate: typing only emits `rel-change`; `value` (and `changed`) update when a row is picked, or when the text exactly matches an item's label or value (case-insensitive) on Enter, Tab, blur or outside click. Free text that matches nothing stays in the field but never becomes `value`.
184
198
  - Setting `value` externally (attribute, property, Angular `writeValue`) shows the matching item's label, or the raw string when no item matches (common with remote data on init) — provide the item in `items` to show its label.
185
199
  - Clearing emits, in order, `rel-clear`, `changed` (`value: ""`, `item: null`) and `rel-change` (`""`), and only when the field actually had text or a value.
@@ -190,3 +204,5 @@ Focus: `focus()` on the host delegates to the inner field. Focusing the field wi
190
204
  - `full-width` layout is keyed off the **attribute** (`:host(:not([full-width]))` renders inline-block) even though the property defaults to `true` and is not reflected — add the `full-width` attribute in markup when you need the block layout. This differs from `rel-textfield`, whose `fullwidth` attribute is reflected.
191
205
  - The suggestion panel mirrors `rel-select`'s listbox (same tokens, radius, shadow, row styling) and is promoted to the browser top layer (native Popover API, `popover="manual"`), so it renders correctly inside transformed or `overflow: hidden` containers such as a centered `rel-modal` or a `rel-drawer`; engines without the Popover API fall back to `position: fixed` with a measure-and-cancel correction. It matches the field width, flips above when short on space, and closes on outside pointerdown or outside scroll.
192
206
  - Only `label`, `control`, `input`, `helper` and `right-icon` are re-exported from the inner `rel-textfield`; its `prefix` / `suffix` parts and slots are not exposed.
207
+ - The field passthroughs are a fixed subset, forwarded as **property** bindings (`.prefixIcon=${…}`), so they are unaffected by attribute-spelling differences between the two components. `rel-textfield` props outside that subset — `prefixText`, `prefixStyle`, `suffixText`, `suffixDivider`, `helperIcon`, `maxLength`, `multiline`, `rows`, `type` — are not forwarded and cannot be set through `rel-autocomplete`.
208
+ - Several of the passthroughs take a **kebab attribute here and an all-lowercase one on `rel-textfield`**: `prefix-icon` / `prefixicon`, `helper-text` / `helpertext`, `error-message` / `errormessage`, `floating-label` / `floatinglabel`, `full-width` / `fullwidth`. Moving markup between the two elements therefore needs the attribute renamed, and a wrong spelling fails silently rather than erroring. `rel-textfield`'s README carries the full table; 3.0 unifies on the kebab spellings used here.
@@ -19,10 +19,10 @@
19
19
  </rel-avatar>
20
20
 
21
21
  <!-- Sizes: xs 20px, sm 24px, md 32px (default), lg 40px -->
22
- <rel-avatar type="text" text="XS" size="xs"></rel-avatar>
23
- <rel-avatar type="text" text="SM" size="sm"></rel-avatar>
24
- <rel-avatar type="text" text="MD" size="md"></rel-avatar>
25
- <rel-avatar type="text" text="LG" size="lg"></rel-avatar>
22
+ <rel-avatar type="text" text="XS" size="extra-small"></rel-avatar>
23
+ <rel-avatar type="text" text="SM" size="small"></rel-avatar>
24
+ <rel-avatar type="text" text="MD" size="medium"></rel-avatar>
25
+ <rel-avatar type="text" text="LG" size="large"></rel-avatar>
26
26
 
27
27
  <!-- Variants -->
28
28
  <rel-avatar type="text" text="AA" variant="fill"></rel-avatar>
@@ -50,7 +50,7 @@
50
50
 
51
51
  | Property | Attribute | Type | Default | Description |
52
52
  | --- | --- | --- | --- | --- |
53
- | `size` | `size` | `"lg" \| "md" \| "sm" \| "xs"` | `"md"` | Diameter step: `lg` 40px, `md` 32px, `sm` 24px, `xs` 20px; each binds the matching body-*-semibold text role for initials. Reflected. |
53
+ | `size` | `size` | `"large" \| "medium" \| "small" \| "extra-small" \| "lg" \| "md" \| "sm" \| "xs"` | `"medium"` | Diameter step: `large` 40px, `medium` 32px, `small` 24px, `extra-small` 20px; each binds the matching body-*-semibold text role for initials. `lg` / `md` / `sm` / `xs` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. Reflected. |
54
54
  | `type` | `type` | `"image" \| "text" \| "icon"` | `"image"` | What is rendered inside the circle: `image` shows `src`, `text` shows the `text` initials, `icon` shows the default slot. Reflected. |
55
55
  | `variant` | `variant` | `"fill" \| "subtle" \| "outline"` | `"fill"` | Fill style: `fill` = solid inverse background with light text, `subtle` = elevated neutral background, `outline` = transparent with a 1px border. Reflected. |
56
56
  | `src` | `src` | `string` | `""` | Image URL for `type="image"`; when empty nothing is rendered inside the circle (there is no fallback to initials). |
@@ -69,7 +69,7 @@
69
69
 
70
70
  | Slot | Description |
71
71
  | --- | --- |
72
- | (default) | Icon content rendered when `type="icon"` (typically an inline SVG, sized to 50% of the circle) |
72
+ | (default) | Icon content rendered when `type="icon"` (typically an inline SVG, sized 20 / 18 / 16 / 12px by `size`) |
73
73
 
74
74
  ## CSS Parts
75
75
 
@@ -88,12 +88,14 @@ Set these on the host (or an ancestor) to customise the element without touching
88
88
  | Custom property | Default | Description |
89
89
  | --- | --- | --- |
90
90
  | `--rel-avatar-bg-fill` | `var(--rel-bg-neutral-inverse-2)` | Background colour of the `fill` variant |
91
+ | `--rel-avatar-bg-outline` | `var(--rel-bg-neutral)` | Background colour of the `outline` variant; defaults to --rel-bg-neutral |
91
92
  | `--rel-avatar-bg-subtle` | `var(--rel-bg-neutral-elevated-1)` | Background colour of the `subtle` variant |
92
93
  | `--rel-avatar-border-outline` | `var(--rel-border-default)` | 1px border colour of the `outline` variant |
93
94
  | `--rel-avatar-color-fill` | `var(--rel-text-inverse)` | Text / icon colour of the `fill` variant |
94
95
  | `--rel-avatar-color-outline` | `var(--rel-text-default)` | Text / icon colour of the `outline` variant |
95
96
  | `--rel-avatar-color-subtle` | `var(--rel-text-default)` | Text / icon colour of the `subtle` variant |
96
97
  | `--rel-avatar-font-size` | `var(--_rel-avatar-font-size)` | Overrides the initials' font-size, for use alongside `--rel-avatar-size`; defaults to the preset's text role |
98
+ | `--rel-avatar-icon-size` | `var(--_rel-avatar-icon-size)` | Overrides the slotted icon's size; defaults to the preset ramp (20 / 18 / 16 / 12px) |
97
99
  | `--rel-avatar-size` | `var(--_rel-avatar-size)` | Overrides the circle's width/height for sizes between (or below) the `size` presets; defaults to the preset's diameter |
98
100
 
99
101
  <!-- readme-gen:end -->
@@ -112,5 +114,5 @@ Set these on the host (or an ancestor) to customise the element without touching
112
114
  - `type="image"` with an empty `src` renders an empty circle — there is no automatic fallback to initials. Switch `type` yourself when the photo is missing.
113
115
  - Every variant keeps a 1px border (`--rel-stroke-sm`); `fill` and `subtle` make it transparent so the outer size is identical across variants.
114
116
  - `bg` and `color` are written as inline styles on the `base` part, so they win over both the `--rel-avatar-*` hooks and the variant tokens.
115
- - Initials are forced uppercase and `user-select: none`. `size`, `type` and `variant` are reflected, so `rel-avatar[size="lg"]` works as a selector.
117
+ - Initials are forced uppercase and `user-select: none`. `size`, `type` and `variant` are reflected, so `rel-avatar[size="large"]` works as a selector. Because `lg` / `md` / `sm` / `xs` are normalised to the full words before reflection, the selector matches whichever spelling was written.
116
118
  - The `icon` part name is shared by the `<img>` (`type="image"`) and the slot wrapper (`type="icon"`); `::part(icon)` matches whichever one is rendered.
@@ -12,9 +12,7 @@
12
12
  <rel-badge number="120" size="xl"></rel-badge>
13
13
 
14
14
  <!-- Dot badges (no text) -->
15
- <rel-badge variant="dot" size="sm"></rel-badge>
16
- <rel-badge variant="dot" size="md"></rel-badge>
17
- <rel-badge variant="dot" size="lg"></rel-badge>
15
+ <rel-badge variant="dot"></rel-badge>
18
16
 
19
17
  <!-- Positioned over an icon button -->
20
18
  <div style="position: relative; display: inline-block;">
@@ -45,8 +43,8 @@
45
43
  | `number` | `number` | `string` | `""` | Text shown in the `number` variant (a count such as "5" or "99+"); not rendered by `variant="dot"` but still used to build the accessible name. |
46
44
  | `ariaLabel` | `arialabel` | `string` | `""` | Explicit accessible name; when empty it is composed as "<number> <suffix>" or, with no number, "New <suffix>". Attribute is `arialabel`. |
47
45
  | `suffix` | `suffix` | `string` | `"notifications"` | Noun appended to the count in the generated accessible name, e.g. "3 notifications". |
48
- | `size` | `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Badge scale (Figma Badge S/M/L/XL): number badges are 20 / 24 / 28 / 40px tall, dot badges 8 / 10 / 12 / 12px. Reflected. |
49
- | `variant` | `variant` | `"dot" \| "number"` | `"number"` | `number` renders the `number` text in a pill that widens with its content; `dot` renders a small disc with no text. Reflected. |
46
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "extra-large" \| "sm" \| "md" \| "lg" \| "xl"` | `"medium"` | Badge scale (Figma Badge S/M/L/XL): number badges are 20 / 24 / 28 / 40px tall. `sm` / `md` / `lg` / `xl` are accepted and normalised to the full words. Ignored when `variant="dot"` — Figma models `Dot` as a fifth option of the same Size axis, so a dot has one size. Reflected. |
47
+ | `variant` | `variant` | `"dot" \| "number"` | `"number"` | `number` renders the `number` text in a pill that widens with its content; `dot` renders a 6px disc with no text (Figma `Size=Dot`), ignoring `size`. Reflected. |
50
48
  | `color` | `color` | `string` | `""` | Direct CSS color override for the badge background. Accepts any valid CSS color value: hex, rgb(...), hsl(...), named color, or var(--token). Clear it (set to `''`) to fall back to the default token. Reflected. |
51
49
  | `textColor` | `text-color` | `string` | `""` | Direct CSS color override for the badge text/icon color. Accepts any valid CSS color value: hex, rgb(...), hsl(...), named color, or var(--token). Clear it (set to `''`) to fall back to the default (white). Reflected. |
52
50
 
@@ -78,6 +76,7 @@ Set these on the host (or an ancestor) to customise the element without touching
78
76
  | --- | --- | --- |
79
77
  | `--rel-badge-bg` | `var(--rel-bg-danger)` | Background colour of the badge; the `color` prop writes this on the host |
80
78
  | `--rel-badge-color` | `var(--rel-text-static-light)` | Text colour of the count; the `text-color` prop writes this on the host |
79
+ | `--rel-badge-dot-size` | `6px` | Diameter of the `dot` variant; default 6px, the single dot size Figma draws. Set it to restore the larger discs shipped previously. |
81
80
 
82
81
  <!-- readme-gen:end -->
83
82
 
@@ -93,7 +93,7 @@ Action button implementing the Figma "Button" component: a native `<button>` wit
93
93
  | Property | Attribute | Type | Default | Description |
94
94
  | --- | --- | --- | --- | --- |
95
95
  | `variant` | `variant` | `"default" \| "primary" \| "secondary" \| "text" \| "link" \| "icon" \| "static" \| "outline" \| "ghost"` | `"default"` | Style axis (Figma "Style"). Canonical: `default` (legacy neutral fill), `primary`, `secondary` (outlined), `ghost` (text-only), `link` (underlined inline text), `static` (transparent, static-light label for dark/colored surfaces). Deprecated aliases still accepted: `outline` = secondary, `text` = ghost, and `icon` (pre-2.0 40px circular icon button — use `icon-only` with a style instead). |
96
- | `tone` | `tone` | `"default" \| "primary" \| "secondary" \| "critical" \| "success" \| "neutral" \| "brand" \| "danger" \| "destructive"` | `"default"` | Tone axis (Figma "Tone"). Canonical: `neutral` (= `default`), `brand` (= `primary`), `destructive`. Deprecated aliases `critical` and `danger` map to destructive and log a one-time console notice. `secondary` and `success` are legacy extras with no Figma counterpart (secondary only affects the legacy `icon` variant). |
96
+ | `tone` | `tone` | `"default" \| "primary" \| "secondary" \| "critical" \| "success" \| "neutral" \| "brand" \| "danger" \| "destructive"` | `undefined` | Tone axis (Figma "Tone"). Canonical: `neutral` (= `default`), `brand` (= `primary`), `destructive`. Deprecated aliases `critical` and `danger` map to destructive and log a one-time console notice. `secondary` and `success` are legacy extras with no Figma counterpart (secondary only affects the legacy `icon` variant). Left UNSET it means `neutral`, except on `variant="primary"`, where it still means `brand` for one release — see `effectiveTone`. Write the tone explicitly and you always get what you wrote. |
97
97
  | `iconOnly` | `icon-only` | `boolean` | `false` | Figma "Type=Icon Only": renders a square button (32/40/48px per `size`) of the current style and tone with no padding or min-width. Composes with every `variant`; slot a single `rel-icon` and set `aria-label`. Reflected. |
98
98
  | `loading` | `loading` | `boolean` | `false` | Figma "State=Loading": replaces the label with a spinner (`indicator` part), sets `aria-busy`, and blocks pointer and programmatic clicks. The button keeps its width. Reflected. |
99
99
  | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Figma "Size": `small` = 32px, `medium` = 40px, `large` = 48px height, with matching padding, typography and minimum width. |
@@ -150,8 +150,38 @@ Focus: `:focus-visible` shows the shared 2px focus outline plus inner/outer halo
150
150
 
151
151
  ## Notes / Edge Cases
152
152
 
153
+ ### The `variant="primary"` tone transition
154
+
155
+ Figma's `Style=Primary` is the **filled** style, and `Tone` picks its colour — so Primary + Neutral is the neutral fill and Primary + Brand is the brand fill. This component's `variant="primary"` historically meant *both* "filled" and "brand-coloured", which left the Figma-canonical Primary/Neutral cell unreachable by its own names.
156
+
157
+ That cell now works: **`variant="primary" tone="neutral"` renders the neutral fill.**
158
+
159
+ For one release there is a deliberate wrinkle, and it is worth knowing before you debug it:
160
+
161
+ | Markup | Renders | Why |
162
+ | --- | --- | --- |
163
+ | `<rel-button>` | neutral fill | Unchanged. `variant` defaults to `default`, which is Figma's Primary/Neutral. |
164
+ | `<rel-button variant="primary">` | **brand** fill | Unset tone still means brand *here only*, so existing brand buttons do not silently turn neutral. Logs a one-time console notice. |
165
+ | `<rel-button variant="primary" tone="brand">` | brand fill | The explicit form. **Write this.** |
166
+ | `<rel-button variant="primary" tone="neutral">` | neutral fill | The Figma-canonical cell, now reachable. |
167
+
168
+ So for this release, writing the default value explicitly changes the result: `variant="primary"` alone and `variant="primary" tone="neutral"` render differently. Setting `tone` explicitly always gives you exactly what you wrote. A future major will make an unset tone mean `neutral` everywhere, matching every other style.
169
+
170
+ ### Tone × Style matrix
171
+
172
+ Figma populates only part of the grid. Combinations outside it have no design-system definition and fall back to the style's neutral treatment:
173
+
174
+ | Tone | Primary | Secondary | Ghost | Link | Static |
175
+ | --- | --- | --- | --- | --- | --- |
176
+ | Neutral | yes | yes | yes | yes | yes |
177
+ | Brand | yes | — | — | — | — |
178
+ | Destructive | yes | yes | yes | yes | — |
179
+
180
+ `Link` additionally has no icon-only and no loading state in the design.
181
+
153
182
  - **Events.** The element dispatches no custom events. The native `click` is `composed`, so a listener on `<rel-button>` (or an ancestor) receives exactly one event per activation — do not re-dispatch it.
154
- - **Minimum widths** come from the Figma master: small/medium/large = 80/100/120px, ghost small = 56px. `link` has no minimum, `icon-only` is a fixed square (32/40/48px) and the legacy `icon` variant is always a 40px circle regardless of `size`. Override per instance with `--rel-button-min-width`.
183
+ - **Minimum widths** come from the Figma master: small/medium/large = 80/100/120px, static = 40/50/60px. `link` has no minimum, `icon-only` is a fixed square (32/40/48px) and the legacy `icon` variant is always a 40px circle regardless of `size`. Override per instance with `--rel-button-min-width`.
184
+ - **`border`** exposes `small-corner-radius` and `no-corner-radius`, which have no design-system backing — every one of Figma's 624 button nodes is fully rounded. They are a code-only extension, kept for existing layouts.
155
185
  - **Legacy `icon` variant vs `icon-only`.** `variant="icon"` keeps its pre-2.0 look (40px circle, tone-driven fill, `tone="secondary"` gives it an outline) and ignores `size` and `border`. `icon-only` is an axis that composes with any style and size — prefer it.
156
186
  - **`tone` on `secondary` / `ghost` / `link`** changes the label, border or text colour only; the fill stays on the surface. The destructive outline border is deliberately subtle at rest and only goes solid on hover/pressed, matching Figma.
157
187
  - **`static`** is a neutral-only style meant for coloured or dark backgrounds; other tones have no static treatment.
@@ -1,6 +1,6 @@
1
1
  # rel-card
2
2
 
3
- `<rel-card>` is a bordered, padded surface that groups related content — the Figma "Card" component. `type="custom"` (the default) pads whatever you slot in and applies the Figma card typography to slotted headings and paragraphs; `type="media"` adds a flush media region above the body, meant for a `<rel-card-media>` image header. Use it for dashboard tiles, summary blocks and list items that pair a title with body copy. It is a static container with no interaction of its own — for transient content use `rel-modal` or `rel-drawer` instead, and for a surface with its own tone/elevation variants use `rel-container`.
3
+ `<rel-card>` is an elevated, padded surface that groups related content — the Figma "Card" component. `type="custom"` (the default) pads whatever you slot in and applies the Figma card typography to slotted headings and paragraphs; `type="media"` adds a flush media region above the body, meant for a `<rel-card-media>` image header. Use it for dashboard tiles, summary blocks and list items that pair a title with body copy. It is a static container with no interaction of its own — for transient content use `rel-modal` or `rel-drawer` instead, and for a surface with its own tone/elevation variants use `rel-container`.
4
4
 
5
5
  ## Usage
6
6
 
@@ -55,7 +55,7 @@
55
55
 
56
56
  | Part | Description |
57
57
  | --- | --- |
58
- | `base` | The outer bordered card surface |
58
+ | `base` | The outer elevated card surface |
59
59
  | `header` | The media region wrapper (present only when `type="media"`) |
60
60
  | `content` | The padded body that wraps the default slot |
61
61
 
@@ -72,4 +72,4 @@
72
72
  - `type="media"` removes the card's own padding and clips overflow so the media sits flush against the rounded border; the body (`content` part) keeps the standard padding. Content placed in the `media` slot without `type="media"` is not rendered, because the slot only exists in that mode.
73
73
  - The `header` part exists only while `type="media"`; `::part(header)` matches nothing otherwise.
74
74
  - Slotted `h1`–`h4` and `p` elements get the Figma card roles (heading md / body lg) with their default margins reset; wrap other content in your own elements to style it.
75
- - There is no hover lift, shadow or transition — the card is a flat bordered surface in both light and dark themes.
75
+ - The card is elevated with Figma's `shadow/sm` and draws no border, in both light and dark themes. There is no hover lift or transition.
@@ -143,7 +143,9 @@ Set these on the host (or an ancestor) to customise the element without touching
143
143
  | `--rel-carousel-dot-size-active` | `16px` | Width of the active (brand-coloured, pill-shaped) dot (default 16px). |
144
144
  | `--rel-carousel-height` | `320px` | Viewport height in `orientation="vertical"` (default 320px); the horizontal viewport takes the height of its tallest slide. |
145
145
  | `--rel-carousel-nav-size` | `32px` | Width / height of the arrow buttons (default 32px, matching Figma; applies to both side and bottom arrows). |
146
+ | `--rel-carousel-slide-bg` | `var(--rel-bg-neutral)` | — |
146
147
  | `--rel-carousel-slide-height` | `100%` | Written by the component on its own host to the computed panel height (used for vertical slides); not meant to be set by consumers. |
148
+ | `--rel-carousel-slide-radius` | `var(--rel-border-radius-sm)` | — |
147
149
  | `--rel-carousel-slide-width` | `100%` | Written by the component on its own host to the computed panel width (viewport width for `panels="1"`, a fraction of it above that); not meant to be set by consumers (an inline value is overwritten on resize). |
148
150
 
149
151
  <!-- readme-gen:end -->
@@ -1,6 +1,6 @@
1
1
  # rel-checkbox
2
2
 
3
- A tri-state checkbox implementing the Figma "Checkbox" sheet: a hidden native `<input type="checkbox">` behind a custom box, with a label, an optional label-prefix icon and optional supporting text. It exposes the sheet's vocabulary verbatim — `size` (small / medium / large), `density` (compact / comfortable) and `alignment` (leading / trailing) — plus `state` for error and disabled styling. Use it for independent yes/no choices and for "select all" parents (via `status="indeterminate"`); for a single choice among several options use `rel-radio`, and for an immediate on/off setting use `rel-switch`.
3
+ A tri-state checkbox implementing the Figma "Checkbox" sheet: a hidden native `<input type="checkbox">` behind a custom box, with a label, an optional label-prefix icon and optional supporting text. It exposes the sheet's vocabulary verbatim — `size` (small / medium / large / extra-large), `density` (compact / comfortable) and `alignment` (leading / trailing) — plus `state` for error and disabled styling. Use it for independent yes/no choices and for "select all" parents (via `status="indeterminate"`); for a single choice among several options use `rel-radio`, and for an immediate on/off setting use `rel-switch`.
4
4
 
5
5
  ## Usage
6
6
 
@@ -64,10 +64,10 @@ A tri-state checkbox implementing the Figma "Checkbox" sheet: a hidden native `<
64
64
  | `label` | `label` | `string` | `"Label"` | Label text rendered next to the box. |
65
65
  | `icon` | `icon` | `string` | `""` | rel-icon name rendered between the checkbox and the label (label prefix icon); scales with `size`. Unset renders no icon and reserves no space. |
66
66
  | `supportingtext` | `supportingtext` | `string` | `""` | Supporting text under or beside the label; where it renders depends on `density`. |
67
- | `state` | `state` | `"default" \| "error" \| "disabled"` | `"default"` | Visual and interaction state (Figma State): `"error"` swaps in the danger colours, `"disabled"` blocks input and dims the control to 0.55 opacity. |
67
+ | `state` | `state` | `"default" \| "error" \| "disabled"` | `"default"` | Visual and interaction state (Figma State): `"error"` swaps in the danger colours, `"disabled"` blocks input and dims the whole control to 0.4 opacity (colours unchanged, as Figma specifies). |
68
68
  | `status` | `status` | `"unchecked" \| "checked" \| "indeterminate"` | `"unchecked"` | Check state. `"indeterminate"` is programmatic-only — a user toggle always resolves to checked or unchecked. |
69
69
  | `value` | `value` | `string` | `""` | Value reported in the `rel-change` event detail (it is not submitted with a form). |
70
- | `size` | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control scale (Figma Size). `"medium"` (default) = 18px box with body-lg label; `"small"` = 14px box with body-md label; `"large"` (Figma L, added 2026-09-04) = 20px box with body-lg label and body-sm supporting text — the same additive step rel-radio took for its large size. Reflected. |
70
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "extra-large"` | `"medium"` | Control scale (Figma Size), on Figma's four-step ramp of control frames: `"small"` 16px / body-sm, `"medium"` (default) 18px / body-md, `"large"` 20px / body-lg, `"extra-large"` 24px / body-xl. The drawn square is inset to 80% of the frame, so the frame is the layout footprint (what the row gap and the compact indent measure from) rather than the visible square. Reflected. |
71
71
  | `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 box; `"comfortable"` stacks label + supporting text beside the box. Reflected. |
72
72
  | `alignment` | `alignment` | `"leading" \| "trailing"` | `"leading"` | Box position (Figma Alignment). `"leading"` (default) renders the box 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. Reflected. |
73
73
 
@@ -121,7 +121,8 @@ A tri-state checkbox implementing the Figma "Checkbox" sheet: a hidden native `<
121
121
  - The host is `display: block`, so a trailing checkbox stretches to its container width; constrain the parent to control row width.
122
122
  - `value` is only reported in the `rel-change` detail — the element does not participate in native form submission.
123
123
  - The hidden native input is absolutely positioned against the `.row` containing block; keep `position: relative` on `.row` (Tab-scroll page-jump fix, shared with rel-radio).
124
- - Rendered box sizes are 18 / 22 / 24px (content + 2px border) for small / medium / large, against Figma's 16 / 18 / 20 outer frames — a known, unaudited oversize on S/M.
124
+ - Control frames are 16 / 18 / 20 / 24px for small / medium / large / extra-large, matching Figma. The frame is the layout footprint — the row gap and the compact indent measure from it — while the drawn square is inset to 80% of the frame (12.8 / 14.4 / 16 / 19.2px), as Figma draws it.
125
+ - The square's 2px border weight is **not** verified against Figma: the control is flattened artwork (a filled vector rather than a frame with a stroke), so the sheet gives the square's outer size but says nothing about its stroke. Carried forward from the previous implementation.
125
126
 
126
127
  ### Layout matrix (mirrors the Figma sheet)
127
128
 
@@ -133,6 +134,8 @@ A tri-state checkbox implementing the Figma "Checkbox" sheet: a hidden native `<
133
134
  | S | Compact | yes | renders comfortable |
134
135
  | L | Comfortable | yes | yes |
135
136
  | L | Compact | yes | renders comfortable |
137
+ | XL | Comfortable | yes | yes |
138
+ | XL | Compact | yes | renders comfortable |
136
139
 
137
140
  ### Recipes (intentionally not component props)
138
141
 
@@ -5,22 +5,28 @@
5
5
  ## Usage
6
6
 
7
7
  ```html
8
- <!-- Basic chip (tone="neutral" variant="fill" size="md" border="default") -->
8
+ <!-- Basic chip (tone="neutral" variant="fill" size="medium" border="default") -->
9
9
  <rel-chip label="Active"></rel-chip>
10
10
 
11
11
  <!-- Tones -->
12
12
  <rel-chip label="Info" tone="info"></rel-chip>
13
13
  <rel-chip label="Warning" tone="warning"></rel-chip>
14
- <rel-chip label="Error" tone="danger"></rel-chip>
14
+ <rel-chip label="Error" tone="destructive"></rel-chip>
15
15
  <rel-chip label="Success" tone="success"></rel-chip>
16
16
 
17
17
  <!-- Subtle variant: tinted background, tone-coloured text -->
18
18
  <rel-chip label="Subtle" tone="info" variant="subtle"></rel-chip>
19
19
 
20
20
  <!-- Sizes -->
21
+ <rel-chip label="Small" size="small"></rel-chip>
22
+ <rel-chip label="Medium" size="medium"></rel-chip>
23
+ <rel-chip label="Large" size="large"></rel-chip>
24
+
25
+ <!-- Deprecated aliases — these still render, but prefer the canonical spellings above.
26
+ tone="danger" is an alias of tone="destructive" and logs a one-time console notice.
27
+ size="sm" / "md" / "lg" are alternative spellings (NOT deprecated, no notice). -->
28
+ <rel-chip label="Error" tone="danger"></rel-chip>
21
29
  <rel-chip label="Small" size="sm"></rel-chip>
22
- <rel-chip label="Medium" size="md"></rel-chip>
23
- <rel-chip label="Large" size="lg"></rel-chip>
24
30
 
25
31
  <!-- Fully rounded corners -->
26
32
  <rel-chip label="Rounded" border="rounded"></rel-chip>
@@ -60,9 +66,9 @@
60
66
  | Property | Attribute | Type | Default | Description |
61
67
  | --- | --- | --- | --- | --- |
62
68
  | `label` | `label` | `string` | `"Label"` | Text shown in the chip; also its accessible name and the drag payload. |
63
- | `tone` | `tone` | `"neutral" \| "danger" \| "warning" \| "success" \| "info"` | `"neutral"` | Colour tone (Figma Tone): `neutral`, `danger`, `warning`, `success` or `info`. Combined with `variant`. Reflected. |
64
- | `variant` | `variant` | `"fill" \| "subtle"` | `"fill"` | Emphasis (Figma Variant): `fill` = solid tone background, `subtle` = tinted background with tone-coloured text. Reflected. |
65
- | `size` | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Height scale (Figma Size): `sm` 24px / body-sm, `md` 28px / body-md, `lg` 32px / body-lg. Icon size follows (12 / 14 / 16px). Reflected. |
69
+ | `tone` | `tone` | `"neutral" \| "destructive" \| "warning" \| "success" \| "info" \| "danger"` | `"neutral"` | Colour tone (Figma Tone): `neutral`, `destructive`, `warning`, `success` or `info`. Combined with `variant`. `"danger"` is a deprecated alias of `"destructive"`: it keeps working, logs a one-time console notice, and is normalised to `"destructive"` on the property and the reflected attribute. Reflected. |
70
+ | `variant` | `variant` | `"fill" \| "subtle" \| "primary" \| "secondary"` | `"fill"` | Emphasis (Figma Style): `fill` = solid tone background, `subtle` = tinted background with tone-coloured text. Figma's own spellings `primary` and `secondary` are accepted and normalised to `fill` / `subtle`. Reflected. |
71
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Height scale (Figma Size): `small` 24px / body-sm, `medium` 28px / body-md, `large` 32px / body-lg. Icon size follows (12 / 14 / 16px). `sm` / `md` / `lg` are accepted and normalised to the full words. Reflected. |
66
72
  | `border` | `border` | `"default" \| "rounded"` | `"default"` | Corner shape (Figma Border): `default` = 4px radius, `rounded` = fully rounded with 12px horizontal padding. Reflected. |
67
73
  | `leadingIcon` | `leading-icon` | `string` | `undefined` | Icon name from the generated icon dictionary (e.g. "settings") — rendered via <rel-icon> before the label. Ignored when `avatar` is set. |
68
74
  | `trailingIcon` | `trailing-icon` | `string` | `undefined` | Icon name from the generated icon dictionary, rendered after the label. |
@@ -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`. |
@@ -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