@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,110 +1,88 @@
1
- # rel-progress
2
-
3
- ## Overview
4
- A progress indicator component supporting four modes: linear-determinate, linear-indeterminate, circular-determinate, and circular-indeterminate. Features smooth animations, proper ARIA attributes, and respects user motion preferences by disabling animations when requested.
5
-
6
- ## Usage
7
- ```html
8
- <!-- Linear progress (determinate) -->
9
- <rel-progress mode="linear-determinate" value="45"></rel-progress>
10
- <rel-progress mode="linear-determinate" value="100"></rel-progress>
11
-
12
- <!-- Linear progress (indeterminate/loading) -->
13
- <rel-progress mode="linear-indeterminate"></rel-progress>
14
-
15
- <!-- Circular progress (determinate) -->
16
- <rel-progress mode="circular-determinate" value="75"></rel-progress>
17
-
18
- <!-- Circular progress (indeterminate/spinner) -->
19
- <rel-progress mode="circular-indeterminate"></rel-progress>
20
-
21
- <!-- Dynamic progress update -->
22
- <rel-progress id="uploadProgress" mode="linear-determinate" value="0"></rel-progress>
23
- <script>
24
- const progress = document.getElementById('uploadProgress');
25
- let value = 0;
26
- const interval = setInterval(() => {
27
- value += 10;
28
- progress.value = value;
29
- if (value >= 100) clearInterval(interval);
30
- }, 500);
31
- </script>
32
- ```
33
-
34
- ## API
35
-
36
- ### Attributes/Properties
37
- | Name | Type | Default | Description |
38
- |------|------|---------|-------------|
39
- | `mode` (reflected) | `"linear-determinate" \| "linear-indeterminate" \| "circular-determinate" \| "circular-indeterminate"` | `"linear-determinate"` | Progress indicator type |
40
- | `value` | `number` | `0` | Progress value (0-100) for determinate modes (ignored in indeterminate modes) |
41
-
42
- ## Events
43
- None.
44
-
45
- ## Slots
46
- Default slot not used.
47
-
48
- ## CSS Parts
49
- None exposed.
50
-
51
- ## Tokens / CSS Variables
52
-
53
- ### Semantic Tokens
54
- - `--rel-fonts-font-family` - Font family
55
-
56
- ### Color Tokens
57
- - `--rel-border-default` - Track/background color
58
- - `--rel-text-default` - Active progress bar color
59
-
60
- ### Border Tokens
61
- - `--rel-border-radius-sm` - Linear track border radius
62
-
63
- ## Accessibility
64
- - Determinate modes use `role="progressbar"` with `aria-valuenow`, `aria-valuemin="0"`, `aria-valuemax="100"`
65
- - Indeterminate modes use `role="progressbar"` without value attributes
66
- - Respects `prefers-reduced-motion: reduce` by disabling animations
67
-
68
- ### Keyboard Interactions
69
- - None (not focusable)
70
-
71
- ### Focus Behavior
72
- - Not focusable
73
-
74
- ## Notes / Edge Cases
75
- - **Linear modes**:
76
- - Height: `6px`
77
- - Track uses gray border color
78
- - Bar uses black color
79
- - Border radius: `--rel-border-radius-sm`
80
- - **Determinate**: bar width transitions smoothly (0.3s ease)
81
- - **Indeterminate**: 30% width bar slides left-to-right (1.5s infinite loop)
82
-
83
- - **Circular modes**:
84
- - Size: `48px × 48px` (viewBox varies by mode)
85
- - Stroke width: `3px` (determinate) or `4px` (indeterminate)
86
- - Stroke linecap: `round`
87
- - **Determinate**:
88
- - Uses `stroke-dasharray` and `stroke-dashoffset` to animate progress
89
- - Circle radius: `15.9155`, circumference: `~100`
90
- - Rotated -90° to start at top
91
- - Progress transition: `0.35s`
92
- - **Indeterminate (spinner)**:
93
- - Rotates continuously (2s linear infinite)
94
- - Stroke dash animates (1.5s ease-in-out infinite)
95
-
96
- - **Value clamping**: Values are automatically clamped to 0-100 range via `safeValue` getter
97
-
98
- - **Motion preferences**:
99
- - When `prefers-reduced-motion: reduce`:
100
- - Linear determinate: no transition
101
- - Linear indeterminate: no animation, shows full-width bar at 50% opacity
102
- - Circular determinate: no transition
103
- - Circular indeterminate: no rotation/dash animation, shows static partial arc
104
-
105
- - **Display modes**:
106
- - Linear modes: `display: block` (full width)
107
- - Circular modes: `display: inline-block` (sized to content)
108
-
109
- - The component automatically switches render methods based on `mode` property
110
- - Indeterminate modes ignore the `value` property
1
+ # rel-progress
2
+
3
+ `<rel-progress>` is the Figma "Progress" indicator in its two shapes and two behaviours: a full-width 2px linear bar or a 36px circular ring, each either determinate (fills to `value`, 0–100) or indeterminate (loops an animation while you wait). Use `linear-determinate` / `circular-determinate` for uploads, imports and multi-step flows where progress is known, and the `*-indeterminate` modes as a loading signal while a request is in flight. It is a passive `role="progressbar"` element — it does not block interaction (use `rel-modal` with `loading` for that) and does not announce completion on its own.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- Linear, determinate -->
9
+ <rel-progress mode="linear-determinate" value="45"></rel-progress>
10
+ <rel-progress mode="linear-determinate" value="100"></rel-progress>
11
+
12
+ <!-- Linear, indeterminate (loading) -->
13
+ <rel-progress mode="linear-indeterminate"></rel-progress>
14
+
15
+ <!-- Circular, determinate -->
16
+ <rel-progress mode="circular-determinate" value="75"></rel-progress>
17
+
18
+ <!-- Circular, indeterminate (spinner) -->
19
+ <rel-progress mode="circular-indeterminate"></rel-progress>
20
+
21
+ <!-- Accessible name -->
22
+ <rel-progress mode="linear-determinate" value="30" label="Uploading invoices"></rel-progress>
23
+ <rel-progress mode="circular-indeterminate" label="Loading shipments"></rel-progress>
24
+
25
+ <!-- Driving the value from script -->
26
+ <rel-progress id="uploadProgress" mode="linear-determinate" value="0" label="Upload"></rel-progress>
27
+ <script>
28
+ const progress = document.getElementById('uploadProgress');
29
+ let value = 0;
30
+ const interval = setInterval(() => {
31
+ value += 10;
32
+ progress.value = value;
33
+ if (value >= 100) clearInterval(interval);
34
+ }, 500);
35
+ </script>
36
+ ```
37
+
38
+ <!-- readme-gen:start -->
39
+
40
+ <!-- Generated from rel-progress.ts — do not edit; run readme-generate.mjs -->
41
+
42
+ ## API
43
+
44
+ ### Attributes / Properties
45
+
46
+ | Property | Attribute | Type | Default | Description |
47
+ | --- | --- | --- | --- | --- |
48
+ | `mode` | `mode` | `"linear-determinate" \| "linear-indeterminate" \| "circular-determinate" \| "circular-indeterminate"` | `"linear-determinate"` | Shape and behaviour: `linear-*` is a full-width 2px bar, `circular-*` a 36px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. Reflected. |
49
+ | `value` | `value` | `number` | `0` | Completion percentage 0–100 for the determinate modes; out-of-range values are clamped before rendering and before `aria-valuenow`. |
50
+ | `label` | `label` | `string` | `""` | Accessible name of the progressbar (not rendered visually); defaults to "Progress" for determinate and "Loading" for indeterminate modes. |
51
+
52
+ ## Events
53
+
54
+ | Event | Detail | Bubbles / composed | Description |
55
+ | --- | --- | --- | --- |
56
+ | — | — | — | This element emits no custom events. |
57
+
58
+ ## Slots
59
+
60
+ | Slot | Description |
61
+ | --- | --- |
62
+ | — | This element has no slots. |
63
+
64
+ ## CSS Parts
65
+
66
+ | Part | Description |
67
+ | --- | --- |
68
+ | `track` | The linear track `<div>` that carries `role="progressbar"` (linear modes only) |
69
+ | `indicator` | The filled / sliding bar inside the linear track (linear modes only) |
70
+
71
+ <!-- readme-gen:end -->
72
+
73
+ ## Accessibility
74
+
75
+ - Every mode renders one element with `role="progressbar"` and an `aria-label` taken from `label`; when `label` is empty the name is "Progress" for determinate modes and "Loading" for indeterminate ones. Give it a specific `label` ("Uploading invoices") when more than one indicator can be on screen.
76
+ - Determinate modes also set `aria-valuenow` (the clamped `value`), `aria-valuemin="0"` and `aria-valuemax="100"`. Indeterminate modes omit the value attributes, which is what tells assistive technology the progress is unknown.
77
+ - The element is not focusable and handles no keys.
78
+ - It does not announce anything by itself: neither the start of loading nor reaching 100% is spoken. Announce those from the surrounding UI (for example with a status message) when they matter.
79
+ - `prefers-reduced-motion: reduce` is respected: the determinate transitions are removed, the linear indeterminate bar becomes a static full-width bar at 50% opacity, and the circular spinner stops rotating and shows a static partial arc.
80
+
81
+ ## Notes / Edge Cases
82
+
83
+ - `value` is clamped to 0–100 before rendering and before `aria-valuenow`; the indeterminate modes ignore it entirely.
84
+ - The `track` and `indicator` parts exist only in the two linear modes. The circular modes render an inline `<svg>` with no parts, so `::part(track)` matches nothing there.
85
+ - Linear bars are 2px tall with a 4px radius and stretch to the host's width (host is `display: block`). Circular rings are fixed at 36px and the host switches to `display: inline-block`, so they sit inline with text.
86
+ - The linear determinate width animates over 0.3s and the circular arc over 0.35s; a value that jumps from 0 to 100 therefore takes a moment to catch up visually.
87
+ - Circular strokes are 3px (determinate) and 4px (indeterminate) with a flat (`butt`) line cap; the determinate ring starts at 12 o'clock via a −90° rotation.
88
+ - The indeterminate linear bar is a 30%-wide segment that sweeps left to right on a 1.5s loop; the spinner rotates on a 2s loop with a 1.5s dash animation.
@@ -1,138 +1,131 @@
1
- # rel-radio
2
-
3
- ## Overview
4
- A radio button group component that allows users to select a single option from a list. Supports individual option states (disabled, error), supporting text, and comprehensive styling with design tokens.
5
-
6
- ## Usage
7
- ```html
8
- <!-- Basic example -->
9
- <rel-radio id="colorRadio" name="color-group" selectedValue="blue"></rel-radio>
10
- <script>
11
- const radio = document.getElementById('colorRadio');
12
- radio.options = [
13
- { value: 'red', label: 'Red' },
14
- { value: 'blue', label: 'Blue' },
15
- { value: 'green', label: 'Green' }
16
- ];
17
- </script>
18
-
19
- <!-- Example with supporting text and states -->
20
- <rel-radio id="planRadio" name="plan-group"></rel-radio>
21
- <script>
22
- const planRadio = document.getElementById('planRadio');
23
- planRadio.options = [
24
- { value: 'basic', label: 'Basic Plan', supportingText: '$9.99/month' },
25
- { value: 'pro', label: 'Pro Plan', supportingText: '$19.99/month' },
26
- { value: 'enterprise', label: 'Enterprise', supportingText: 'Contact sales', disabled: true }
27
- ];
28
- planRadio.selectedValue = 'basic';
29
- </script>
30
-
31
- <!-- Horizontal row layout -->
32
- <rel-radio id="sizeRadio" name="size-group" orientation="horizontal"></rel-radio>
33
- <script>
34
- const sizeRadio = document.getElementById('sizeRadio');
35
- sizeRadio.options = [
36
- { value: 's', label: 'Small' },
37
- { value: 'm', label: 'Medium' },
38
- { value: 'l', label: 'Large' }
39
- ];
40
- </script>
41
-
42
- <!-- Listen for changes -->
43
- <rel-radio id="preferenceRadio"></rel-radio>
44
- <script>
45
- const prefRadio = document.getElementById('preferenceRadio');
46
- prefRadio.options = [
47
- { value: 'email', label: 'Email' },
48
- { value: 'sms', label: 'SMS' }
49
- ];
50
- prefRadio.addEventListener('changed', (e) => {
51
- console.log('Selected:', prefRadio.selectedValue);
52
- });
53
- </script>
54
- ```
55
-
56
- ## API
57
-
58
- ### Attributes/Properties
59
- | Name | Type | Default | Description |
60
- |------|------|---------|-------------|
61
- | `options` | `RadioOption[]` | `[]` | Array of radio options (see RadioOption interface below) |
62
- | `name` | `string` | `"radio-group"` | Name attribute for the radio group (for form grouping) |
63
- | `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Layout axis: `"vertical"` stacks options in a column; `"horizontal"` lays them out in a wrapping row (reflected attribute) |
64
- | `size` | `"small" \| "medium" \| "large"` | `"medium"` | Control scale (Figma Size L/M/S): `"medium"` = 20px control with body-lg labels; `"large"` = 24px control with body-xl labels; `"small"` = 20px control with body-md labels — S and M share the control size, only typography steps down (reflected attribute) |
65
- | `density` | `"compact" \| "comfortable"` | `"compact"` | Supporting-text layout (Figma Density): `"compact"` puts the supporting text on its own line under the row, indented past the control; `"comfortable"` stacks label + supporting text beside the control (reflected attribute) |
66
- | `alignment` | `"leading" \| "trailing"` | `"leading"` | Control position (Figma Layout): `"trailing"` pushes the control to the far edge of the row and always renders comfortable density — the spec has no compact trailing layout (reflected attribute) |
67
- | `selectedValue` | `string \| null` | `null` | Currently selected option value (reflected attribute) |
68
- | `disabled` | `boolean` | `false` | Disables the entire radio group (renders at 40% opacity) |
69
-
70
- ### RadioOption Interface
71
- ```typescript
72
- interface RadioOption {
73
- value: string; // Unique value for this option
74
- label: string; // Display label
75
- supportingText?: string; // Optional helper text below the label
76
- disabled?: boolean; // Disable individual option
77
- icon?: string; // rel-icon name rendered before the label
78
- avatar?: string; // 1–2 initials rendered as a circular chip before the label (ignored when icon is set)
79
- }
80
- ```
81
-
82
- ## Events
83
- | Event | Detail | Description |
84
- |-------|--------|-------------|
85
- | `changed` | None (read `selectedValue` property) | Fired when the selected option changes |
86
-
87
- ## Slots
88
- Default slot not used.
89
-
90
- ## CSS Parts
91
- None exposed.
92
-
93
- ## Tokens / CSS Variables
94
-
95
- ### Semantic Tokens
96
- - `--rel-fonts-font-family` - Font family
97
- - `--rel-body-sm-regular-font-size`, `--rel-body-xs-regular-font-size` - Font sizes for label and supporting text
98
- - `--rel-fonts-weight-body-medium` - Font weight for labels
99
-
100
- ### Spacing Tokens
101
- - `--rel-spacing-xs` - Gap between radio options (also the row gap when wrapping horizontally)
102
- - `--rel-spacing-xl` - Column gap between options in `orientation="horizontal"`
103
- - `--rel-spacing-3xs` - Gap within each option (label and supporting text)
104
- - `--rel-spacing-2xs` - Gap between radio circle and label
105
-
106
- ### Color Tokens
107
- - `--rel-text-label` - Radio button border and checked dot color
108
- - `--rel-text-default` - Label text color
109
- - `--rel-text-placeholder` - Supporting text color
110
- - `--rel-border-info` - Focus outline color
111
- - `--rel-text-danger` - Error state border and dot color
112
-
113
- ## Accessibility
114
- - The option list renders with `role="radiogroup"` and `aria-orientation` reflecting the `orientation` prop
115
- - Uses visually hidden native radio inputs for proper screen reader support
116
- - Custom visual radio circle with proper focus indicators
117
- - Focus visible state with outline offset for keyboard navigation
118
- - Label click targets the radio for larger interaction area
119
- - Disabled state communicated through reduced opacity and cursor change
120
- - Proper `name` grouping ensures only one option can be selected
121
-
122
- ### Keyboard Interactions
123
- - `Tab` - Move focus to/from radio group
124
- - `Arrow Up/Down` - Navigate between radio options
125
- - `Space` - Select focused option
126
-
127
- ### Focus Behavior
128
- - Focus outline appears with offset around the custom radio circle
129
- - Uses info border color for high contrast
130
-
131
- ## Notes / Edge Cases
132
- - The `selectedValue` property is reflected as an attribute and can be set programmatically or via HTML
133
- - When `disabled` is true on the component, all options are disabled regardless of individual option settings
134
- - Individual options can be disabled via the `disabled` property in the `RadioOption` object
135
- - The `error` property on `RadioOption` changes the radio button color to red but does not prevent selection
136
- - Supporting text is optional and appears below the label with left margin alignment
137
- - Native radio inputs are visually hidden but maintain all accessibility features
138
- - The `name` attribute groups radios together for form submission and mutual exclusion
1
+ # rel-radio
2
+
3
+ `<rel-radio>` is the single-select radio group of the Reliability Design System (Figma "Radio"). One element is one group: it renders a native radio input for every entry in `options`, with optional supporting text and an icon or avatar prefix, in the Figma Size (S/M/L), Density (compact / comfortable) and Layout (leading / trailing) variants, plus error and disabled states. Use it when the user must pick exactly one of a few visible choices (2–7). For long lists use `<rel-select>`, for independent on/off choices use `<rel-checkbox>` or `<rel-switch>`.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- Basic group; options as a JSON attribute, preselected via selectedvalue -->
9
+ <rel-radio
10
+ name="color"
11
+ field-aria-label="Colour"
12
+ options='[{"value":"red","label":"Red"},{"value":"blue","label":"Blue"},{"value":"green","label":"Green"}]'
13
+ selectedvalue="blue"
14
+ ></rel-radio>
15
+
16
+ <!-- Two independent groups on one page: give each its own name -->
17
+ <rel-radio id="plan" name="plan" field-aria-label="Plan"></rel-radio>
18
+ <rel-radio id="billing" name="billing" field-aria-label="Billing cycle" orientation="horizontal"></rel-radio>
19
+
20
+ <!-- Sizes (S and M share the 20px control; L is 24px) -->
21
+ <rel-radio id="small" name="size-small" size="small"></rel-radio>
22
+ <rel-radio id="medium" name="size-medium" size="medium"></rel-radio>
23
+ <rel-radio id="large" name="size-large" size="large"></rel-radio>
24
+
25
+ <!-- Supporting text beside the control instead of on its own line -->
26
+ <rel-radio id="comfortable" name="tier" density="comfortable"></rel-radio>
27
+
28
+ <!-- Control on the far edge of each row (always comfortable density) -->
29
+ <rel-radio id="trailing" name="notify" alignment="trailing"></rel-radio>
30
+
31
+ <!-- Error state with a message; disabled group -->
32
+ <rel-radio id="consent" name="consent" error errormessage="Choose one option to continue"></rel-radio>
33
+ <rel-radio id="locked" name="locked" disabled></rel-radio>
34
+ ```
35
+
36
+ ```js
37
+ const plan = document.getElementById("plan");
38
+ plan.options = [
39
+ { value: "basic", label: "Basic", supportingText: "$9.99 / month" },
40
+ { value: "pro", label: "Pro", supportingText: "$19.99 / month", icon: "settings" },
41
+ { value: "team", label: "Team", avatar: "TM" },
42
+ { value: "enterprise", label: "Enterprise", supportingText: "Contact sales", disabled: true },
43
+ ];
44
+ plan.selectedValue = "basic";
45
+
46
+ plan.addEventListener("rel-change", (e) => {
47
+ e.detail.value; // "pro" — the selected option's value
48
+ e.detail.checked; // always true
49
+ plan.selectedValue === e.detail.value; // already updated
50
+ });
51
+ ```
52
+
53
+ <!-- readme-gen:start -->
54
+
55
+ <!-- Generated from rel-radio.ts — do not edit; run readme-generate.mjs -->
56
+
57
+ ## API
58
+
59
+ ### Attributes / Properties
60
+
61
+ | Property | Attribute | Type | Default | Description |
62
+ | --- | --- | --- | --- | --- |
63
+ | `options` | `options` | `RadioOption[]` | `[]` | The options to render, in order. Each entry is a `RadioOption` (`value`, `label`, optional `supportingText`, `disabled`, `icon` or `avatar`). Set as a property (`.options=`) or as a JSON attribute. |
64
+ | `name` | `name` | `string` | `"radio-group"` | `name` given to every native radio input inside this element. It scopes native radio-group behaviour (arrow-key movement, mutual exclusion) to this group, so give each group on a page a distinct name. |
65
+ | `orientation` | `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Layout axis of the option list. `"vertical"` (default) stacks options in a column; `"horizontal"` lays them out in a wrapping row. Reflected. |
66
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Control scale. `"medium"` (default, Figma M) = 20px control with body-lg labels; `"large"` (Figma L) = 24px control with body-xl labels; `"small"` = 16px control with body-md labels. `sm` / `md` / `lg` are alternative spellings and stay supported — normalised to the full words, not deprecated. DELIBERATE FIGMA DIVERGENCE on Small. Figma's `.Radio Control` has one size (20x20) and its row variants pin Small AND Medium to 20, so the two rendered identically and the Size axis did nothing between them. 16px is not invented: it is rel-checkbox's Small, and checkbox is radio's sibling selection control. See the size ramp in the styles below. Reflected. |
67
+ | `density` | `density` | `"compact" \| "comfortable"` | `"compact"` | Supporting-text layout (Figma Density). `"compact"` (default) puts the supporting text on its own line under the row, indented past the control; `"comfortable"` stacks label + supporting text beside the control. Reflected. |
68
+ | `alignment` | `alignment` | `"leading" \| "trailing"` | `"leading"` | Control position (Figma Layout). `"leading"` (default) renders the control before the label; `"trailing"` pushes it to the far edge of the row. Trailing has no compact layout in the spec, so it always renders with comfortable density. Meant for vertical orientation — trailing options stretch to the group's full width. Reflected. |
69
+ | `selectedValue` | `selectedvalue` | `string \| null` | `null` | `value` of the currently selected option, or `null` when nothing is selected. Set it to preselect an option; it is updated on every user selection. Reflected. |
70
+ | `disabled` | `disabled` | `boolean` | `false` | Disables every option (Figma State=Disabled: the whole group at 40% opacity). Individual options can also be disabled via `RadioOption.disabled`. |
71
+ | `error` | `error` | `boolean` | `false` | Error state (Figma State=Error): rings, dots and supporting text turn danger. Applies to every option while nothing is selected, and only to the selected option once a choice is made. Selection is still allowed. |
72
+ | `errorMessage` | `errormessage` | `string` | `""` | Message rendered under the group (live region) while `error` is set; hidden when `error` is false or the text is empty. |
73
+ | `fieldAriaLabel` | `field-aria-label` | `string` | `undefined` | Accessible name for the radiogroup (it renders no visible group label). |
74
+ | `value` | `value` | `string` | `""` | Value reported to the Angular forms bridge (`registerOnChange` callback) on each change and compared by `writeValue`. Not used for rendering; read `selectedValue` or the `rel-change` detail for the selected option. |
75
+
76
+ ### Public fields / accessors
77
+
78
+ | Name | Type | Description |
79
+ | --- | --- | --- |
80
+ | `checked` | `boolean` | Set by the Angular forms bridge (`writeValue`) to whether the written form value equals `value`. Not used for rendering — read `selectedValue` for the selection. |
81
+
82
+ ## Events
83
+
84
+ | Event | Detail | Bubbles / composed | Description |
85
+ | --- | --- | --- | --- |
86
+ | `rel-change` | `{ value, checked: true }` | bubbles / composed | Fired when the user selects an option (after `selectedValue` is updated). Detail: `{ value: string, checked: true }` — `value` is the selected option's value. |
87
+
88
+ ## Slots
89
+
90
+ | Slot | Description |
91
+ | --- | --- |
92
+ | — | This element has no slots. |
93
+
94
+ ## CSS Parts
95
+
96
+ | Part | Description |
97
+ | --- | --- |
98
+ | `label` | The visible label text of each option |
99
+ | `base` | The `role="radiogroup"` container that lays out the options |
100
+
101
+ <!-- readme-gen:end -->
102
+
103
+ ## Accessibility
104
+
105
+ - The option container (part `base`) is a `role="radiogroup"` with `aria-orientation` = `orientation`, `aria-label` = `field-aria-label` (set it — the group renders no visible legend) and `aria-describedby` pointing at the error line.
106
+ - Every option is a native `<input type="radio">` inside a `<label>`, so the whole row (control, label, supporting text) is the click target and the input's accessible name is the option label. The input is visually hidden (`opacity: 0`, absolutely positioned over the row) but stays in the Tab order; the drawn ring / dot mirror its `:checked` state.
107
+ - Disabled options get the native `disabled` attribute (skipped by Tab and arrow keys) and render at 40% opacity; `disabled` on the host disables all of them.
108
+ - The error message is a persistent `aria-live="polite"` region under the group, so a message that appears after submission is read out.
109
+ - Avatar chips are `aria-hidden`; a prefix icon is a `<rel-icon>` and decorative.
110
+ - Keyboard focus is shown as a 1.5px `--rel-border-focus` outline (1px offset) around the focused option; it appears only for `:focus-visible` (keyboard) focus.
111
+ - Keyboard behaviour is the browser's native radio behaviour — the component adds no key handlers of its own:
112
+
113
+ | Key | Action |
114
+ | --- | --- |
115
+ | Tab / Shift + Tab | Enter the group (on the checked option, else the first enabled one) or leave it |
116
+ | ArrowDown / ArrowRight | Move to the next enabled option and select it (fires `rel-change`) |
117
+ | ArrowUp / ArrowLeft | Move to the previous enabled option and select it (fires `rel-change`) |
118
+ | Space | Select the focused option if it is not yet selected |
119
+
120
+ ## Notes / Edge Cases
121
+
122
+ - `RadioOption` shape: `{ value: string; label: string; supportingText?: string; disabled?: boolean; icon?: string; avatar?: string }`. `icon` is a `rel-icon` name rendered before the label (Figma Has Icon/Avatar, Type=Icon); `avatar` is 1–2 initials rendered as a circular chip and is ignored when `icon` is also set.
123
+ - `rel-change` fires only on user selection. Setting `selectedValue` from code (or via the `selectedvalue` attribute) updates the UI silently.
124
+ - Grouping by `name`: the native inputs of one `<rel-radio>` share `name`, which is what gives arrow-key movement and mutual exclusion; they live in the element's shadow root, so they are not submitted with a surrounding `<form>`. Read `selectedValue` or listen to `rel-change` to collect the value. Give every group on a page its own `name` so the intent is unambiguous.
125
+ - Error rules: while nothing is selected, every option shows the danger ring; once an option is selected only that option does. Labels keep their default colour; rings, dots and supporting text turn `--rel-border-danger` / `--rel-text-danger`. Error never blocks selection.
126
+ - `alignment="trailing"` always renders comfortable density. Combining it with `density="compact"` logs `[rel-radio] density="compact" has no trailing layout; rendering comfortable density.` (a `console.warn`, every time either prop changes).
127
+ - Compact supporting text is indented past the control box: 28px (S/M) and 32px (L). Comfortable density widens the control-to-label gap from 8px to 12px.
128
+ - Hover shifts the ring, dot, label and prefix icon from `--rel-text-default` to `--rel-text-body` on enabled, non-error options.
129
+ - `value` and `checked` exist for the Angular forms bridge (`registerOnChange` / `registerOnTouched` / `writeValue`, which are internal API). The bridge callback receives the `value` property, not the selected option — plain DOM consumers should ignore both and use `selectedValue` / `rel-change`.
130
+ - `<rel-menu type="select-option">` renders an `inert`, `aria-hidden` `<rel-radio>` inside each row purely as a visual control; that usage does not fire `rel-change`.
131
+ - The hidden native input is absolutely positioned inside a `position: relative` row on purpose — without that containing block, tabbing to it scrolls the wrong ancestor and jumps the page (shared fix with `rel-checkbox`).