@reliability-design/web 1.4.6 → 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 (148) hide show
  1. package/CHANGELOG.md +246 -49
  2. package/dist/components/rel-accordion/README.md +133 -132
  3. package/dist/components/rel-alert/README.md +145 -141
  4. package/dist/components/rel-autocomplete/README.md +172 -61
  5. package/dist/components/rel-avatar/README.md +118 -130
  6. package/dist/components/rel-badge/README.md +97 -93
  7. package/dist/components/rel-breadcrumb/README.md +96 -102
  8. package/dist/components/rel-button/README.md +190 -130
  9. package/dist/components/rel-calendar/README.md +109 -109
  10. package/dist/components/rel-card/README.md +75 -94
  11. package/dist/components/rel-cardmedia/README.md +100 -127
  12. package/dist/components/rel-carousel/README.md +186 -166
  13. package/dist/components/rel-checkbox/README.md +96 -75
  14. package/dist/components/rel-chip/README.md +138 -121
  15. package/dist/components/rel-container/README.md +107 -99
  16. package/dist/components/rel-date-picker/README.md +200 -289
  17. package/dist/components/rel-divider/README.md +88 -100
  18. package/dist/components/rel-drawer/README.md +187 -200
  19. package/dist/components/rel-kbd/README.md +141 -151
  20. package/dist/components/rel-layout/README.md +34 -0
  21. package/dist/components/rel-menu/README.md +180 -151
  22. package/dist/components/rel-menubar/README.md +218 -329
  23. package/dist/components/rel-otp/README.md +171 -440
  24. package/dist/components/rel-pagination/README.md +100 -424
  25. package/dist/components/rel-popover/README.md +203 -568
  26. package/dist/components/rel-progress/README.md +88 -110
  27. package/dist/components/rel-radio/README.md +131 -138
  28. package/dist/components/rel-range-picker/README.md +165 -0
  29. package/dist/components/rel-resizable/README.md +199 -140
  30. package/dist/components/rel-segmented-button/README.md +156 -141
  31. package/dist/components/rel-select/README.md +156 -93
  32. package/dist/components/rel-sheet/README.md +168 -224
  33. package/dist/components/rel-skeleton/README.md +104 -0
  34. package/dist/components/rel-slider/README.md +131 -135
  35. package/dist/components/rel-snackbar/README.md +168 -134
  36. package/dist/components/rel-stepper/README.md +162 -160
  37. package/dist/components/rel-switch/README.md +117 -102
  38. package/dist/components/rel-tab/README.md +173 -163
  39. package/dist/components/rel-textfield/README.md +176 -69
  40. package/dist/components/rel-time-picker/README.md +122 -157
  41. package/dist/components/rel-toast/README.md +165 -148
  42. package/dist/components/rel-tooltip/README.md +119 -111
  43. package/dist/components/rel-upload/README.md +220 -0
  44. package/dist/index.d.mts +1726 -447
  45. package/dist/index.d.ts +1726 -447
  46. package/dist/index.js +13715 -10870
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +14018 -11163
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/styles/focus-ring.ts +84 -0
  51. package/dist/styles/vars.css +892 -866
  52. package/dist/{vars-XNMTSRVN.css → vars-DXX6QOAG.css} +892 -866
  53. package/package.json +3 -2
  54. package/dist/components/rel-accordion/rel-accordion.ts +0 -336
  55. package/dist/components/rel-alert/rel-alert.ts +0 -305
  56. package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -720
  57. package/dist/components/rel-avatar/rel-avatar.ts +0 -142
  58. package/dist/components/rel-badge/rel-badge.ts +0 -175
  59. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
  60. package/dist/components/rel-button/rel-button.ts +0 -560
  61. package/dist/components/rel-calendar/index.ts +0 -1
  62. package/dist/components/rel-calendar/rel-calendar.ts +0 -565
  63. package/dist/components/rel-card/rel-card.ts +0 -88
  64. package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
  65. package/dist/components/rel-carousel/index.ts +0 -1
  66. package/dist/components/rel-carousel/rel-carousel.ts +0 -723
  67. package/dist/components/rel-chart/README.md +0 -286
  68. package/dist/components/rel-chart/index.ts +0 -1
  69. package/dist/components/rel-chart/rel-chart.ts +0 -754
  70. package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
  71. package/dist/components/rel-chip/rel-chip.ts +0 -483
  72. package/dist/components/rel-container/rel-container.ts +0 -95
  73. package/dist/components/rel-date-picker/date-format.test.ts +0 -43
  74. package/dist/components/rel-date-picker/date-format.ts +0 -47
  75. package/dist/components/rel-date-picker/date-parse.test.ts +0 -116
  76. package/dist/components/rel-date-picker/date-parse.ts +0 -178
  77. package/dist/components/rel-date-picker/index.ts +0 -1
  78. package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1676
  79. package/dist/components/rel-divider/rel-divider.ts +0 -54
  80. package/dist/components/rel-drawer/index.ts +0 -1
  81. package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
  82. package/dist/components/rel-drawer/rel-drawer.ts +0 -607
  83. package/dist/components/rel-kbd/rel-kbd.ts +0 -188
  84. package/dist/components/rel-layout/rel-layout.ts +0 -49
  85. package/dist/components/rel-menu/rel-menu.ts +0 -541
  86. package/dist/components/rel-menubar/index.ts +0 -2
  87. package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
  88. package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
  89. package/dist/components/rel-menubar/rel-menubar.ts +0 -777
  90. package/dist/components/rel-nav-menu/README.md +0 -289
  91. package/dist/components/rel-nav-menu/index.ts +0 -3
  92. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
  93. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
  94. package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
  95. package/dist/components/rel-navbar/README.md +0 -126
  96. package/dist/components/rel-navbar/rel-navbar.ts +0 -105
  97. package/dist/components/rel-otp/index.ts +0 -1
  98. package/dist/components/rel-otp/rel-otp.ts +0 -527
  99. package/dist/components/rel-pagination/index.ts +0 -1
  100. package/dist/components/rel-pagination/rel-pagination.ts +0 -522
  101. package/dist/components/rel-popover/index.ts +0 -2
  102. package/dist/components/rel-popover/rel-popover-content.ts +0 -81
  103. package/dist/components/rel-popover/rel-popover.ts +0 -475
  104. package/dist/components/rel-progress/rel-progress.ts +0 -234
  105. package/dist/components/rel-radio/rel-radio.ts +0 -481
  106. package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1688
  107. package/dist/components/rel-resizable/index.ts +0 -3
  108. package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
  109. package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
  110. package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
  111. package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -217
  112. package/dist/components/rel-select/rel-select.ts +0 -1416
  113. package/dist/components/rel-sheet/rel-sheet.ts +0 -610
  114. package/dist/components/rel-sidebar/README.md +0 -209
  115. package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1191
  116. package/dist/components/rel-skeleton/rel-skeleton.ts +0 -143
  117. package/dist/components/rel-slider/rel-slider.ts +0 -673
  118. package/dist/components/rel-snackbar/rel-snackbar.ts +0 -329
  119. package/dist/components/rel-stepper/rel-stepper.ts +0 -567
  120. package/dist/components/rel-switch/rel-switch.ts +0 -199
  121. package/dist/components/rel-tab/rel-tab.ts +0 -264
  122. package/dist/components/rel-table/README.md +0 -178
  123. package/dist/components/rel-table/rel-table-body.ts +0 -22
  124. package/dist/components/rel-table/rel-table-caption.ts +0 -30
  125. package/dist/components/rel-table/rel-table-cell.ts +0 -43
  126. package/dist/components/rel-table/rel-table-footer.ts +0 -24
  127. package/dist/components/rel-table/rel-table-head.ts +0 -47
  128. package/dist/components/rel-table/rel-table-header.ts +0 -22
  129. package/dist/components/rel-table/rel-table-row.ts +0 -37
  130. package/dist/components/rel-table/rel-table.ts +0 -62
  131. package/dist/components/rel-textfield/rel-textfield.ts +0 -1054
  132. package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1482
  133. package/dist/components/rel-time-picker/time-parse.test.ts +0 -67
  134. package/dist/components/rel-time-picker/time-parse.ts +0 -85
  135. package/dist/components/rel-toast/rel-toast.ts +0 -206
  136. package/dist/components/rel-tooltip/rel-tooltip.ts +0 -193
  137. package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
  138. package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
  139. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
  140. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
  141. package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
  142. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
  143. package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
  144. package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
  145. package/dist/components/rel-topbar/rel-topbar.ts +0 -446
  146. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
  147. package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
  148. package/dist/components/rel-upload/rel-upload.ts +0 -968
@@ -1,97 +1,208 @@
1
1
  # rel-autocomplete
2
2
 
3
- ## Overview
4
-
5
- A searchable single-select type-ahead field ("autocomplete") for searchable-dropdown and searchbar scenarios. Composes `rel-textfield` for all field chrome (label, sizes, variants, states, icons) and adds a suggestion panel with keyboard navigation, loading / no-results states, and clear/reset.
6
-
7
- Two data modes:
8
-
9
- - **Local** — provide `items`; typing filters them client-side (`filter` mode).
10
- - **Remote** — provide a `searchFn`; input is debounced (`debounce-ms`), queries are latest-wins (stale responses are discarded), and local filtering is bypassed (results arrive pre-filtered).
3
+ `<rel-autocomplete>` is a searchable single-select type-ahead field. It composes `rel-textfield` for all field chrome (label, sizes, variants, states, prefix icon, helper / error text) and adds a suggestion panel with keyboard navigation, loading and no-results rows, and an optional clear affordance. Two data modes: **local** — set `items` and typing filters them client-side (`filter`); **remote** — set `searchFn` and input is debounced (`debounce-ms`), queries are latest-wins and local filtering is bypassed. Use it for searchable dropdowns and search bars over lists too long for `rel-select`; for a short fixed list use `rel-select`, and for plain free text use `rel-textfield`.
11
4
 
12
5
  ## Usage
13
6
 
14
7
  ```html
15
8
  <!-- Local items (strings) -->
16
- <rel-autocomplete id="carrier" label="Carrier" prefix-icon="search" clearable></rel-autocomplete>
9
+ <rel-autocomplete id="carrier" label="Carrier" prefix-icon="search" placeholder="Start typing" clearable></rel-autocomplete>
17
10
  <script>
18
11
  const ac = document.getElementById("carrier");
19
12
  ac.items = ["FedEx Freight", "UPS Freight", "XPO Logistics", "Estes Express"];
20
13
  ac.addEventListener("changed", (e) => console.log(e.detail.value, e.detail.item));
14
+ ac.addEventListener("rel-change", (e) => console.log("typed:", e.detail));
21
15
  </script>
22
16
 
23
- <!-- Object items with icons -->
17
+ <!-- Object items with icons, read through display-field / value-field -->
18
+ <rel-autocomplete id="mode" label="Mode" display-field="name" value-field="id"></rel-autocomplete>
24
19
  <script>
25
- ac.items = [
26
- { value: "air", label: "Air", icon: "air" },
27
- { value: "sea", label: "Sea", icon: "ship" },
20
+ document.getElementById("mode").items = [
21
+ { id: "air", name: "Air", icon: "air" },
22
+ { id: "sea", name: "Sea", icon: "ship" },
23
+ // dulled, skipped by arrow keys, ignores clicks
24
+ { id: "rail", name: "Rail", icon: "intermodal", disabled: true },
28
25
  ];
29
26
  </script>
30
27
 
31
- <!-- Custom fields for arbitrary API shapes -->
32
- <rel-autocomplete display-field="name" value-field="id"></rel-autocomplete>
28
+ <!-- Filtering and limits -->
29
+ <rel-autocomplete label="City" filter="starts-with" max-suggestions="8" min-search-length="2"></rel-autocomplete>
33
30
 
34
- <!-- Remote data -->
31
+ <!-- Remote data: debounced, latest-wins -->
32
+ <rel-autocomplete id="remote" label="Customer" debounce-ms="250" loading-text="Searching…" no-results-text="No customers match"></rel-autocomplete>
35
33
  <script>
36
- ac.searchFn = async (query) => {
37
- const res = await fetch(`/api/carriers?q=${encodeURIComponent(query)}`);
34
+ const remote = document.getElementById("remote");
35
+ remote.searchFn = async (query) => {
36
+ const res = await fetch(`/api/customers?q=${encodeURIComponent(query)}`);
38
37
  return res.json(); // (string | object)[]
39
38
  };
39
+ remote.addEventListener("rel-search-error", (e) => console.warn(e.detail.query, e.detail.error));
40
40
  </script>
41
+
42
+ <!-- Field chrome passthroughs -->
43
+ <rel-autocomplete label="Small underline" size="small" variant="underline"></rel-autocomplete>
44
+ <rel-autocomplete label="Floating label" label-style="floating" helper-text="Pick a carrier"></rel-autocomplete>
45
+ <rel-autocomplete label-style="none" placeholder="Search orders" field-aria-label="Search orders"></rel-autocomplete>
46
+ <rel-autocomplete label="Required" required error error-message="Choose a carrier from the list"></rel-autocomplete>
47
+ <rel-autocomplete label="Read only" value="UPS Freight" readonly></rel-autocomplete>
48
+ <rel-autocomplete label="Disabled" disabled></rel-autocomplete>
49
+ <rel-autocomplete label="Inline width" full-width name="carrier"></rel-autocomplete>
50
+ ```
51
+
52
+ ### Deprecated aliases
53
+
54
+ ```html
55
+ <!-- floating-label → label-style="floating" (kept permanently as an alias; no console notice) -->
56
+ <rel-autocomplete label="Old spelling" floating-label></rel-autocomplete>
57
+ <rel-autocomplete label="Canonical" label-style="floating"></rel-autocomplete>
41
58
  ```
42
59
 
60
+ <!-- readme-gen:start -->
61
+
62
+ <!-- Generated from rel-autocomplete.ts — do not edit; run readme-generate.mjs -->
63
+
43
64
  ## API
44
65
 
45
- ### Attributes/Properties
46
-
47
- | Name | Type | Default | Description |
48
- |------|------|---------|-------------|
49
- | `items` | `(string \| object)[]` | `[]` | Local suggestion list. Objects are read through `display-field` / `value-field`; an `icon` field renders a leading `rel-icon` on the row |
50
- | `value` | `string` | `""` | Committed value — the selected item's `value-field` string. Typing alone does not change it; selection (or an exact label/value match on Enter/blur) does. Settable externally: the field shows the matching item's label, or the raw string when unknown |
51
- | `display-field` | `string` | `"label"` | Object key used as the row label |
52
- | `value-field` | `string` | `"value"` | Object key used as the committed value |
53
- | `filter` | `"contains" \| "starts-with" \| "none"` | `"contains"` | Local filtering mode. `"none"` shows `items` as-is (use when the app pre-filters). Ignored when `searchFn` is set |
54
- | `searchFn` | `(query: string) => Promise<items>` | — | Property-only. Async data source; enables debounce + loading + latest-wins handling |
55
- | `debounce-ms` | `number` | `300` | Debounce before `searchFn` fires |
56
- | `min-search-length` | `number` | `1` | Minimum typed characters before the panel opens / `searchFn` fires. `0` opens on focus showing all items |
57
- | `max-suggestions` | `number` | `Infinity` | Optional cap on rendered rows (the panel scrolls regardless) |
58
- | `clearable` | `boolean` | `false` | Trailing ✕ clears text + value, emits `rel-clear` and `changed` |
59
- | `loading` | `boolean` | `false` | Externally-controlled loading row (internal `searchFn` loading is automatic) |
60
- | `no-results-text` | `string` | `"No results found"` | Row shown when a query yields nothing |
61
- | `loading-text` | `string` | `"Loading…"` | Text next to the loading spinner |
62
- | `label`, `placeholder`, `size`, `variant`, `label-style`, `floating-label`, `field-aria-label`, `field-aria-labelledby`, `prefix-icon`, `disabled`, `readonly`, `required`, `error`, `error-message`, `helper-text`, `name`, `full-width` | — | — | Passed through to the inner `rel-textfield` unchanged |
63
- | `label-style` | `"default" \| "floating" \| "none"` | — | Label presentation. `"none"` renders no label — pair it with `field-aria-label`. Unset, the deprecated `floating-label` boolean decides |
66
+ ### Attributes / Properties
67
+
68
+ | Property | Attribute | Type | Default | Description |
69
+ | --- | --- | --- | --- | --- |
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
+ | `value` | `value` | `string` | `""` | Committed value — the selected item's valueField string ("" = none). |
72
+ | `displayField` | `display-field` | `string` | `"label"` | Object key read for the row label when items are objects. |
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. |
75
+ | `filter` | `filter` | `"contains" \| "starts-with" \| "none"` | `"contains"` | Local filtering mode. Ignored when searchFn is set (results arrive pre-filtered). |
76
+ | `searchFn` | — | `(query: string) => Promise<AutocompleteItem[]>` | `undefined` | Async data source. When set, local filtering is bypassed entirely. Property only. |
77
+ | `debounceMs` | `debounce-ms` | `number` | `300` | Debounce for searchFn calls, in ms. |
78
+ | `minSearchLength` | `min-search-length` | `number` | `1` | Minimum typed characters before the panel opens / searchFn fires. 0 = open on focus. |
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. |
83
+ | `clearable` | `clearable` | `boolean` | `false` | Shows a trailing ✕ that clears the text and the committed value. |
84
+ | `loading` | `loading` | `boolean` | `false` | Externally-controlled loading state (internal searchFn loading is automatic). |
85
+ | `noResultsText` | `no-results-text` | `string` | `"No results found"` | Row shown when a query yields nothing. |
86
+ | `loadingText` | `loading-text` | `string` | `"Loading…"` | Text shown next to the spinner while suggestions load. |
87
+ | `label` | `label` | `string` | `undefined` | Visible label, forwarded to the inner `rel-textfield`. Also names the suggestion panel. |
88
+ | `placeholder` | `placeholder` | `string` | `""` | Placeholder of the input, 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. |
90
+ | `variant` | `variant` | `"default" \| "underline" \| "legend"` | `"default"` | Field chrome (`default` outlined, `underline`, `legend` notched outline), forwarded to the inner `rel-textfield`. |
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. |
92
+ | `fieldAriaLabel` | `field-aria-label` | `string` | `undefined` | Accessible name for the input. Required when `labelStyle="none"`. |
93
+ | `fieldAriaLabelledby` | `field-aria-labelledby` | `string` | `undefined` | IDREF list of external element(s) naming the input. Wins over `fieldAriaLabel`. |
94
+ | `prefixIcon` | `prefix-icon` | `string` | `undefined` | Leading `rel-icon` name in the field's prefix block, e.g. `search`. |
95
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the field; the panel never opens and the clear affordance is hidden. |
96
+ | `readonly` | `readonly` | `boolean` | `false` | Read-only field: focusable but not editable; the panel never opens and the clear affordance is hidden. |
97
+ | `required` | `required` | `boolean` | `false` | Marks the input required (native `required` / `aria-required`, asterisk on the label). |
98
+ | `error` | `error` | `boolean` | `false` | Puts the field in the error state (danger border, `aria-invalid`); pair with `errorMessage`. |
99
+ | `errorMessage` | `error-message` | `string` | `undefined` | Text shown below the field instead of `helperText` while `error` is set. |
100
+ | `helperText` | `helper-text` | `string` | `undefined` | Helper line below the field, linked to the input via `aria-describedby`. |
101
+ | `name` | `name` | `string` | `undefined` | Form field name forwarded to the native input. |
102
+ | `fullWidth` | `full-width` | `boolean` | `true` | Fills the parent width as a block element (default). Unset it to render inline at the content's natural width. |
103
+
104
+ #### Deprecated (still supported — removal targeted for 3.0)
105
+
106
+ | Property | Attribute | Type | Use instead |
107
+ | --- | --- | --- | --- |
108
+ | `floatingLabel` | `floating-label` | `boolean` | Use `labelStyle="floating"`. Kept permanently as an alias. |
109
+
110
+ ### Methods
111
+
112
+ | Method | Signature | Description |
113
+ | --- | --- | --- |
114
+ | `focus` | `focus()` | Delegates focus to the inner textfield. |
64
115
 
65
116
  ## Events
66
117
 
67
- | Event | Detail | Description |
68
- |-------|--------|-------------|
69
- | `changed` | `{ value: string, item: object \| string \| null }` | An item was selected (or cleared — `value: ""`, `item: null`). This is the form-value event (Angular CVA binds it) |
70
- | `rel-change` | `string` | Fired on every keystroke with the raw text — use for the controlled/remote pattern (fetch, then set `.items`) |
71
- | `rel-clear` | — | The clear (✕) affordance was used |
72
- | `rel-search-error` | `{ query, error }` | `searchFn` rejected — the panel shows the no-results state |
118
+ | Event | Detail | Bubbles / composed | Description |
119
+ | --- | --- | --- | --- |
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. |
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. |
122
+ | `rel-clear` | — | bubbles / composed | Fired when the clear icon empties a non-empty field (followed by `changed` and `rel-change`). No detail. |
123
+ | `rel-search-error` | `{ query, error: err }` | bubbles / composed | Fired when `searchFn` rejects; the panel then shows the no-results row. Detail: `{ query, error }`. |
124
+
125
+ ## Slots
126
+
127
+ | Slot | Description |
128
+ | --- | --- |
129
+ | — | This element has no slots. |
130
+
131
+ ## CSS Parts
132
+
133
+ | Part | Description |
134
+ | --- | --- |
135
+ | `chips` | — |
136
+ | `chip` | — |
137
+ | `panel` | The suggestion listbox container (same node as `list`) |
138
+ | `list` | Alias of `panel` |
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 |
143
+ | `label` | The inner field's label (re-exported from `rel-textfield`) |
144
+ | `control` | The inner field's bordered control box (re-exported from `rel-textfield`) |
145
+ | `input` | The inner native input (re-exported from `rel-textfield`) |
146
+ | `helper` | The inner field's helper / error line (re-exported from `rel-textfield`) |
147
+ | `right-icon` | The clear icon shown when `clearable` (re-exported from `rel-textfield`) |
148
+
149
+ ## Tokens / CSS Variables
150
+
151
+ ### Component hooks
152
+
153
+ Set these on the host (or an ancestor) to customise the element without touching design tokens.
154
+
155
+ | Custom property | Default | Description |
156
+ | --- | --- | --- |
157
+ | `--rel-autocomplete-bg` | `var(--rel-bg-neutral)` | Panel and row background |
158
+ | `--rel-autocomplete-divider` | `transparent` | Hairline between rows |
159
+ | `--rel-autocomplete-icon-color` | `var(--rel-text-default)` | Colour of a row's leading `icon` |
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. |
162
+ | `--rel-autocomplete-menu-radius` | `var(--rel-border-radius-xs)` | Panel corner radius |
163
+ | `--rel-autocomplete-menu-shadow` | `var(--rel-shadows-md)` | Panel box-shadow |
164
+ | `--rel-autocomplete-option-hover` | `var(--rel-action-overlay-hover)` | Background of the hovered / keyboard-highlighted / selected row |
165
+ | `--rel-autocomplete-option-pressed` | `var(--rel-action-overlay-pressed)` | Background of a row while it is being pressed |
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)` | — |
168
+ | `--rel-autocomplete-value-color` | `var(--rel-text-default)` | Suggestion label colour |
169
+
170
+ <!-- readme-gen:end -->
73
171
 
74
- ## Keyboard Interactions
172
+ ## Accessibility
75
173
 
76
- - Typing — filters (local) or queries (remote) and opens the panel
77
- - `Arrow Down` / `Arrow Up` — open the panel / move the highlight
78
- - `Enter` — selects the highlighted row; with no highlight, commits an exact label/value match, otherwise keeps the free text and closes
79
- - `Escape` — closes the panel without touching the text
80
- - `Tab` — commits an exact match, closes, and moves on
174
+ - Combobox pattern: a wrapper around the inner field carries `role="combobox"`, `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls` and `aria-activedescendant` (the highlighted row). The role sits on the wrapper rather than the `<input>` because the input lives in `rel-textfield`'s nested shadow root, which `aria-activedescendant` cannot reference across — the pragmatic pattern for composed shadow components.
175
+ - The panel is `role="listbox"` named by the visible `label` (or `field-aria-label` under `label-style="none"`, falling back to "Suggestions"); rows are `role="option"` with `aria-selected` on the committed value. The loading row is `role="status"`.
176
+ - Field-level semantics come from `rel-textfield`: `<label for>`, `aria-required`, `aria-invalid`, `aria-describedby` on the helper / error line. With `label-style="none"` the inner field logs a console warning once if neither `field-aria-label` nor `field-aria-labelledby` is set.
177
+ - The clear affordance (`clearable`) is the inner field's clickable trailing icon: `role="button"`, focusable, labelled "Clear".
178
+ - Screen-reader announcements via the shared `announce()` helper: `"Loading suggestions"` when the loading row appears, `"{label}, i of n"` while arrowing through rows, `"N results available"` when results change, and the `no-results-text` when a query yields nothing.
179
+ - Disabled and read-only fields never open the panel and hide the clear icon.
81
180
 
82
- ## Accessibility
181
+ | Key | Action |
182
+ |-----|--------|
183
+ | Typing | Filters local items or queries `searchFn`, and opens the panel once the query reaches `min-search-length` |
184
+ | `ArrowDown` | Open the panel (if the query is long enough) or move the highlight down |
185
+ | `ArrowUp` | Move the highlight up |
186
+ | `Enter` | Select the highlighted row; with no highlight, commit an exact label / value match, otherwise keep the free text and close |
187
+ | `Escape` | Close the panel without touching the text |
188
+ | `Tab` | Commit an exact match (if any), close, and move focus on |
83
189
 
84
- - Combobox pattern: `role="combobox"` with `aria-expanded`, `aria-controls`, and `aria-activedescendant`; panel is `role="listbox"` with `role="option"` rows
85
- - Known limitation: the combobox role sits on a wrapper around the field rather than on the `<input>` itself — the input lives in `rel-textfield`'s nested shadow root, which `aria-activedescendant` cannot reference across. This is the standard pragmatic pattern for composed shadow components
86
- - Loading row uses `role="status"`; disabled/readonly never open the panel
87
- - With `label-style="none"` no `<label>` renders, so `field-aria-label` (or `field-aria-labelledby`) must supply the accessible name — it also names the suggestion panel. A placeholder is not a valid naming mechanism; the inner field logs a console warning when both are missing
190
+ Focus: `focus()` on the host delegates to the inner field. Focusing the field with `min-search-length="0"` (or with a query already long enough and matching items) opens the panel. Focus leaving the component commits an exact match and closes the panel; the same happens on an outside pointerdown. After a clear, focus returns to the field.
88
191
 
89
192
  ## Notes / Edge Cases
90
193
 
91
- - The matched substring in each row renders bold
92
- - `value` set externally to a string not present in `items` displays the raw string (common with remote data on init); provide the item in `items` to show its label instead
93
- - `searchFn` rejection is treated as an empty result plus a `rel-search-error` event — the component never throws into the app
94
- - Form validation is *displayed* (`error`, `error-message`, `required`), not performed — validity logic belongs to the form layer (e.g. Angular validators)
95
- - CSS parts: `panel`, `list`, `option`, plus re-exported textfield parts (`label`, `control`, `input`, `helper`, `right-icon`)
96
- - Panel styling matches `rel-select`'s listbox; override via `--rel-autocomplete-bg`, `--rel-autocomplete-menu-border`, `--rel-autocomplete-menu-radius`, `--rel-autocomplete-menu-shadow`, `--rel-autocomplete-option-hover`
97
- - The open panel is promoted to the browser **top layer** (native Popover API, `popover="manual"`), so it renders correctly inside transformed / `overflow: hidden` containers — a centered `rel-modal`, a bottom `rel-drawer`. Engines without the Popover API fall back to `position: fixed` with a measure-and-cancel correction for transformed ancestors
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.
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`.
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.
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.
200
+ - Remote mode: `items` is ignored while `searchFn` has produced results; a shorter-than-minimum query cancels any in-flight request and closes the panel; stale responses are discarded (latest-wins); a rejected `searchFn` is treated as an empty result plus `rel-search-error` — the component never throws into the app.
201
+ - `loading` is an external flag layered on top of the automatic `searchFn` loading state; the loading row is shown while either is true.
202
+ - `max-suggestions` caps rendered rows only; the panel scrolls (360px max height) regardless. The matched substring in each row renders bold.
203
+ - Validation is displayed (`error`, `error-message`, `required`) but not performed; validity logic belongs to the form layer.
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.
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.
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.
@@ -1,130 +1,118 @@
1
- # rel-avatar
2
-
3
- ## Overview
4
- A circular avatar component supporting three display types: image, text initials, or custom icon content. Provides four size variants and three style variants (fill, subtle, outline), with automatic text transformation and SVG sizing for icons.
5
-
6
- ## Usage
7
- ```html
8
- <!-- Image avatar -->
9
- <rel-avatar
10
- type="image"
11
- src="./user-photo.jpg"
12
- alt="John Doe">
13
- </rel-avatar>
14
-
15
- <!-- Text initials avatar -->
16
- <rel-avatar
17
- type="text"
18
- text="JD">
19
- </rel-avatar>
20
-
21
- <!-- Icon avatar with slotted SVG -->
22
- <rel-avatar type="icon">
23
- <svg viewBox="0 0 24 24" fill="currentColor">
24
- <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
25
- </svg>
26
- </rel-avatar>
27
-
28
- <!-- Different sizes -->
29
- <rel-avatar type="text" text="XS" size="xs"></rel-avatar>
30
- <rel-avatar type="text" text="SM" size="sm"></rel-avatar>
31
- <rel-avatar type="text" text="MD" size="md"></rel-avatar>
32
- <rel-avatar type="text" text="LG" size="lg"></rel-avatar>
33
-
34
- <!-- Style variants -->
35
- <rel-avatar type="text" text="AA" variant="fill"></rel-avatar>
36
- <rel-avatar type="text" text="AA" variant="subtle"></rel-avatar>
37
- <rel-avatar type="text" text="AA" variant="outline"></rel-avatar>
38
-
39
- <!-- User list with avatars -->
40
- <div style="display: flex; gap: 8px; align-items: center;">
41
- <rel-avatar type="image" src="user1.jpg" size="sm"></rel-avatar>
42
- <span>John Doe</span>
43
- </div>
44
-
45
- <!-- Avatar group -->
46
- <div style="display: flex; margin-left: -8px;">
47
- <rel-avatar type="text" text="A" size="md" style="margin-left: -8px;"></rel-avatar>
48
- <rel-avatar type="text" text="B" size="md" style="margin-left: -8px;"></rel-avatar>
49
- <rel-avatar type="text" text="C" size="md" style="margin-left: -8px;"></rel-avatar>
50
- </div>
51
- ```
52
-
53
- ## API
54
-
55
- ### Attributes/Properties
56
- | Name | Type | Default | Description |
57
- |------|------|---------|-------------|
58
- | `size` (reflected) | `"lg" \| "md" \| "sm" \| "xs"` | `"md"` | Avatar size variant |
59
- | `type` (reflected) | `"image" \| "text" \| "icon"` | `"image"` | Display type |
60
- | `variant` (reflected) | `"fill" \| "subtle" \| "outline"` | `"fill"` | Style variant |
61
- | `src` | `string` | `""` | Image URL (for `type="image"`) |
62
- | `text` | `string` | `""` | Text content (for `type="text"`, usually initials) |
63
- | `alt` | `string` | `"Avatar"` | Alt text for the image |
64
- | `bg` | `string` | `""` | Direct CSS background-color override (any valid CSS color), takes precedence over `variant` |
65
-
66
- ## Events
67
- None.
68
-
69
- ## Slots
70
- | Slot | Description |
71
- |------|-------------|
72
- | (default) | Custom icon content (for `type="icon"`, typically an SVG) |
73
-
74
- ## CSS Parts
75
- None exposed.
76
-
77
- ## Tokens / CSS Variables
78
-
79
- ### Semantic Tokens
80
- - `--rel-fonts-font-family` - Font family
81
-
82
- ### Font Tokens
83
- - `--rel-headings-lg-font-size` - Large avatar text size
84
- - `--rel-body-xl-regular-font-size` - Medium avatar text size
85
- - `--rel-body-md-regular-font-size` - Small avatar text size
86
- - `--rel-body-xs-regular-font-size` - Extra small avatar text size
87
- - `--rel-fonts-weight-body-semibold` - Text font weight
88
-
89
- ### Color Tokens (by variant)
90
- - **fill** (default): `--rel-avatar-bg-fill` (default `--rel-text-disabled`), `--rel-avatar-color-fill` (default `#ffffff`)
91
- - **subtle**: `--rel-avatar-bg-subtle` (default `--rel-bg-neutral-elevated-1`), `--rel-avatar-color-subtle` (default `--rel-text-label`)
92
- - **outline**: `--rel-avatar-bg-outline` (default `--rel-bg-surface`), `--rel-avatar-border-outline` (default `--rel-border-default`), `--rel-avatar-color-outline` (default `--rel-text-label`)
93
- - `--rel-stroke-sm` - Border width (all variants; `fill`/`subtle` use a transparent border to keep sizing consistent)
94
-
95
- All of the above semantic tokens (`--rel-bg-*`, `--rel-text-label`, `--rel-border-*`, `--rel-text-disabled`) have dark-mode overrides defined in `vars.css` under `:root[data-theme="dark"]`, so all three variants automatically adapt to dark mode with no extra configuration.
96
-
97
- ## Accessibility
98
- - Image avatars include `alt` attribute for screen readers
99
- - Text is automatically uppercased for consistency
100
- - Ensure meaningful `alt` text is provided for image avatars
101
-
102
- ### Keyboard Interactions
103
- - None (purely presentational)
104
-
105
- ### Focus Behavior
106
- - Does not receive focus
107
-
108
- ## Notes / Edge Cases
109
- - **Size variants**:
110
- - **lg**: 40px × 40px, font-size: `--rel-headings-lg-font-size`
111
- - **md**: 32px × 32px, font-size: `--rel-body-xl-regular-font-size`
112
- - **sm**: 24px × 24px, font-size: `--rel-body-md-regular-font-size`
113
- - **xs**: 18px × 18px, font-size: `--rel-body-xs-regular-font-size`
114
-
115
- - **Display types**:
116
- - **image**: Displays an `<img>` with `src` and `alt`
117
- - Uses `object-fit: cover` for proper image scaling
118
- - Image is `width: 100%`, `height: 100%`
119
- - **text**: Displays the `text` property in a `<span>`
120
- - Text is automatically uppercased via `text-transform: uppercase`
121
- - **icon**: Displays slotted content (typically SVG)
122
- - Slotted SVGs are automatically sized to 50% of the avatar size
123
-
124
- - The avatar is always circular via `border-radius: 50%`
125
- - The avatar uses `overflow: hidden` to clip images to circle shape
126
- - Text and icons are centered using flexbox
127
- - The avatar is `inline-flex` for easy inline usage with text
128
- - The `size` and `type` attributes are reflected in HTML
129
- - The component uses `user-select: none` to prevent text selection
130
- - If `type` is `image` but `src` is empty, nothing is displayed (no fallback)
1
+ # rel-avatar
2
+
3
+ `<rel-avatar>` is the circular identity mark of the Figma "Avatar" component: a 20–40px disc that shows a photo (`type="image"`), uppercase initials (`type="text"`) or a slotted icon (`type="icon"`), in a solid `fill`, a neutral `subtle` or a bordered `outline` variant. Use it wherever a person, team or account needs a compact visual identity — user menus, comment threads, assignee columns, stacked avatar groups. It is purely presentational: it takes no focus and emits no events, so wrap it in a button or link when it should be clickable, and overlay a `rel-badge` when it needs a status or count.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- Image avatar (always pass a meaningful alt) -->
9
+ <rel-avatar type="image" src="./user-photo.jpg" alt="Priya Natarajan"></rel-avatar>
10
+
11
+ <!-- Initials -->
12
+ <rel-avatar type="text" text="PN"></rel-avatar>
13
+
14
+ <!-- Slotted icon -->
15
+ <rel-avatar type="icon">
16
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
17
+ <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
18
+ </svg>
19
+ </rel-avatar>
20
+
21
+ <!-- Sizes: xs 20px, sm 24px, md 32px (default), lg 40px -->
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
+
27
+ <!-- Variants -->
28
+ <rel-avatar type="text" text="AA" variant="fill"></rel-avatar>
29
+ <rel-avatar type="text" text="AA" variant="subtle"></rel-avatar>
30
+ <rel-avatar type="text" text="AA" variant="outline"></rel-avatar>
31
+
32
+ <!-- Inline colour overrides (any CSS colour) -->
33
+ <rel-avatar type="text" text="JD" bg="#1d4ed8" color="#ffffff"></rel-avatar>
34
+
35
+ <!-- Stacked group -->
36
+ <div style="display: flex; padding-left: 8px;">
37
+ <rel-avatar type="text" text="A" style="margin-left: -8px;"></rel-avatar>
38
+ <rel-avatar type="text" text="B" style="margin-left: -8px;"></rel-avatar>
39
+ <rel-avatar type="text" text="C" style="margin-left: -8px;"></rel-avatar>
40
+ </div>
41
+ ```
42
+
43
+ <!-- readme-gen:start -->
44
+
45
+ <!-- Generated from rel-avatar.ts — do not edit; run readme-generate.mjs -->
46
+
47
+ ## API
48
+
49
+ ### Attributes / Properties
50
+
51
+ | Property | Attribute | Type | Default | Description |
52
+ | --- | --- | --- | --- | --- |
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
+ | `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
+ | `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
+ | `src` | `src` | `string` | `""` | Image URL for `type="image"`; when empty nothing is rendered inside the circle (there is no fallback to initials). |
57
+ | `text` | `text` | `string` | `""` | Initials shown for `type="text"`; rendered uppercase via `text-transform`. |
58
+ | `alt` | `alt` | `string` | `"Avatar"` | Alt text of the `<img>` for `type="image"`; the default is the generic word "Avatar", so pass the person's name. |
59
+ | `bg` | `bg` | `string` | `""` | Any CSS colour applied inline as `background-color` on the circle, overriding the variant background. |
60
+ | `color` | `color` | `string` | `""` | Any CSS colour applied inline as `color` on the circle (initials / icon), overriding the variant text colour. |
61
+
62
+ ## Events
63
+
64
+ | Event | Detail | Bubbles / composed | Description |
65
+ | --- | --- | --- | --- |
66
+ | — | — | — | This element emits no custom events. |
67
+
68
+ ## Slots
69
+
70
+ | Slot | Description |
71
+ | --- | --- |
72
+ | (default) | Icon content rendered when `type="icon"` (typically an inline SVG, sized 20 / 18 / 16 / 12px by `size`) |
73
+
74
+ ## CSS Parts
75
+
76
+ | Part | Description |
77
+ | --- | --- |
78
+ | `base` | The circular wrapper that carries the size, variant and inline `bg` / `color` overrides |
79
+ | `icon` | The `<img>` (`type="image"`) or the icon wrapper span around the slot (`type="icon"`) |
80
+ | `label` | The `<span>` holding the initials when `type="text"` |
81
+
82
+ ## Tokens / CSS Variables
83
+
84
+ ### Component hooks
85
+
86
+ Set these on the host (or an ancestor) to customise the element without touching design tokens.
87
+
88
+ | Custom property | Default | Description |
89
+ | --- | --- | --- |
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 |
92
+ | `--rel-avatar-bg-subtle` | `var(--rel-bg-neutral-elevated-1)` | Background colour of the `subtle` variant |
93
+ | `--rel-avatar-border-outline` | `var(--rel-border-default)` | 1px border colour of the `outline` variant |
94
+ | `--rel-avatar-color-fill` | `var(--rel-text-inverse)` | Text / icon colour of the `fill` variant |
95
+ | `--rel-avatar-color-outline` | `var(--rel-text-default)` | Text / icon colour of the `outline` variant |
96
+ | `--rel-avatar-color-subtle` | `var(--rel-text-default)` | Text / icon colour of the `subtle` variant |
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) |
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 |
100
+
101
+ <!-- readme-gen:end -->
102
+
103
+ ## Accessibility
104
+
105
+ - The host has no ARIA role and no keyboard handling, and it is never focusable. Put it inside a `<button>` or `<a>` when it should be interactive and give that element the accessible name.
106
+ - `type="image"` renders a real `<img>` whose alt text comes from the `alt` prop. The default is the generic word "Avatar" — pass the person's name, or an empty string when the avatar sits next to visible text with the same name.
107
+ - `type="text"` renders the initials as plain text in the `label` part, so screen readers read the letters ("P N"). If the initials alone are meaningless, add `aria-hidden="true"` and keep the full name in nearby text.
108
+ - `type="icon"` slots your SVG unchanged; add `aria-hidden="true"` to decorative icons or a `<title>` to meaningful ones.
109
+ - The three variants differ only by colour; every colour used has a dark-mode value in `vars.css`, so no extra theming is needed.
110
+
111
+ ## Notes / Edge Cases
112
+
113
+ - The disc is `border-radius: 50%` with `overflow: hidden`; images use `object-fit: cover`, so non-square sources are centre-cropped rather than stretched.
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.
115
+ - Every variant keeps a 1px border (`--rel-stroke-sm`); `fill` and `subtle` make it transparent so the outer size is identical across variants.
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.
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.
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.