@reliability-design/web 1.4.5 → 1.4.7

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 (142) hide show
  1. package/CHANGELOG.md +211 -49
  2. package/dist/components/rel-accordion/README.md +132 -132
  3. package/dist/components/rel-alert/README.md +145 -141
  4. package/dist/components/rel-autocomplete/README.md +156 -61
  5. package/dist/components/rel-avatar/README.md +116 -130
  6. package/dist/components/rel-badge/README.md +98 -93
  7. package/dist/components/rel-breadcrumb/README.md +96 -102
  8. package/dist/components/rel-button/README.md +160 -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 +184 -166
  13. package/dist/components/rel-checkbox/README.md +93 -75
  14. package/dist/components/rel-chip/README.md +132 -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 +78 -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-menu/README.md +178 -151
  21. package/dist/components/rel-menubar/README.md +218 -329
  22. package/dist/components/rel-otp/README.md +170 -440
  23. package/dist/components/rel-pagination/README.md +100 -424
  24. package/dist/components/rel-popover/README.md +203 -568
  25. package/dist/components/rel-progress/README.md +88 -110
  26. package/dist/components/rel-radio/README.md +131 -135
  27. package/dist/components/rel-range-picker/README.md +162 -0
  28. package/dist/components/rel-resizable/README.md +199 -140
  29. package/dist/components/rel-segmented-button/README.md +155 -141
  30. package/dist/components/rel-select/README.md +154 -93
  31. package/dist/components/rel-sheet/README.md +168 -224
  32. package/dist/components/rel-skeleton/README.md +103 -0
  33. package/dist/components/rel-slider/README.md +130 -135
  34. package/dist/components/rel-snackbar/README.md +162 -134
  35. package/dist/components/rel-stepper/README.md +158 -160
  36. package/dist/components/rel-switch/README.md +108 -102
  37. package/dist/components/rel-tab/README.md +171 -163
  38. package/dist/components/rel-textfield/README.md +159 -69
  39. package/dist/components/rel-time-picker/README.md +122 -157
  40. package/dist/components/rel-toast/README.md +157 -148
  41. package/dist/components/rel-tooltip/README.md +108 -103
  42. package/dist/components/rel-upload/README.md +220 -0
  43. package/dist/index.d.mts +1387 -350
  44. package/dist/index.d.ts +1387 -350
  45. package/dist/index.js +5863 -3600
  46. package/dist/index.js.map +1 -1
  47. package/dist/index.mjs +5864 -3592
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/styles/vars.css +877 -866
  50. package/dist/{vars-XNMTSRVN.css → vars-JHTUGNRG.css} +877 -866
  51. package/package.json +4 -3
  52. package/dist/components/rel-accordion/rel-accordion.ts +0 -336
  53. package/dist/components/rel-alert/rel-alert.ts +0 -305
  54. package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -689
  55. package/dist/components/rel-avatar/rel-avatar.ts +0 -142
  56. package/dist/components/rel-badge/rel-badge.ts +0 -170
  57. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
  58. package/dist/components/rel-button/rel-button.ts +0 -560
  59. package/dist/components/rel-calendar/index.ts +0 -1
  60. package/dist/components/rel-calendar/rel-calendar.ts +0 -519
  61. package/dist/components/rel-card/rel-card.ts +0 -88
  62. package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
  63. package/dist/components/rel-carousel/index.ts +0 -1
  64. package/dist/components/rel-carousel/rel-carousel.ts +0 -700
  65. package/dist/components/rel-chart/README.md +0 -286
  66. package/dist/components/rel-chart/index.ts +0 -1
  67. package/dist/components/rel-chart/rel-chart.ts +0 -754
  68. package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
  69. package/dist/components/rel-chip/rel-chip.ts +0 -472
  70. package/dist/components/rel-container/rel-container.ts +0 -95
  71. package/dist/components/rel-date-picker/date-format.test.ts +0 -43
  72. package/dist/components/rel-date-picker/date-format.ts +0 -47
  73. package/dist/components/rel-date-picker/index.ts +0 -1
  74. package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1237
  75. package/dist/components/rel-divider/rel-divider.ts +0 -54
  76. package/dist/components/rel-drawer/index.ts +0 -1
  77. package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
  78. package/dist/components/rel-drawer/rel-drawer.ts +0 -604
  79. package/dist/components/rel-kbd/rel-kbd.ts +0 -188
  80. package/dist/components/rel-layout/rel-layout.ts +0 -49
  81. package/dist/components/rel-menu/rel-menu.ts +0 -535
  82. package/dist/components/rel-menubar/index.ts +0 -2
  83. package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
  84. package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
  85. package/dist/components/rel-menubar/rel-menubar.ts +0 -777
  86. package/dist/components/rel-nav-menu/README.md +0 -289
  87. package/dist/components/rel-nav-menu/index.ts +0 -3
  88. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
  89. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
  90. package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
  91. package/dist/components/rel-navbar/README.md +0 -126
  92. package/dist/components/rel-navbar/rel-navbar.ts +0 -105
  93. package/dist/components/rel-otp/index.ts +0 -1
  94. package/dist/components/rel-otp/rel-otp.ts +0 -517
  95. package/dist/components/rel-pagination/index.ts +0 -1
  96. package/dist/components/rel-pagination/rel-pagination.ts +0 -509
  97. package/dist/components/rel-popover/index.ts +0 -2
  98. package/dist/components/rel-popover/rel-popover-content.ts +0 -81
  99. package/dist/components/rel-popover/rel-popover.ts +0 -444
  100. package/dist/components/rel-progress/rel-progress.ts +0 -225
  101. package/dist/components/rel-radio/rel-radio.ts +0 -305
  102. package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1331
  103. package/dist/components/rel-resizable/index.ts +0 -3
  104. package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
  105. package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
  106. package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
  107. package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -213
  108. package/dist/components/rel-select/rel-select.ts +0 -1385
  109. package/dist/components/rel-sheet/rel-sheet.ts +0 -465
  110. package/dist/components/rel-sidebar/README.md +0 -209
  111. package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1185
  112. package/dist/components/rel-skeleton/rel-skeleton.ts +0 -134
  113. package/dist/components/rel-slider/rel-slider.ts +0 -670
  114. package/dist/components/rel-snackbar/rel-snackbar.ts +0 -325
  115. package/dist/components/rel-stepper/rel-stepper.ts +0 -565
  116. package/dist/components/rel-switch/rel-switch.ts +0 -197
  117. package/dist/components/rel-tab/rel-tab.ts +0 -264
  118. package/dist/components/rel-table/README.md +0 -178
  119. package/dist/components/rel-table/rel-table-body.ts +0 -22
  120. package/dist/components/rel-table/rel-table-caption.ts +0 -30
  121. package/dist/components/rel-table/rel-table-cell.ts +0 -43
  122. package/dist/components/rel-table/rel-table-footer.ts +0 -24
  123. package/dist/components/rel-table/rel-table-head.ts +0 -47
  124. package/dist/components/rel-table/rel-table-header.ts +0 -22
  125. package/dist/components/rel-table/rel-table-row.ts +0 -37
  126. package/dist/components/rel-table/rel-table.ts +0 -62
  127. package/dist/components/rel-textfield/rel-textfield.ts +0 -866
  128. package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1126
  129. package/dist/components/rel-toast/rel-toast.ts +0 -204
  130. package/dist/components/rel-tooltip/rel-tooltip.ts +0 -190
  131. package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
  132. package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
  133. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
  134. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
  135. package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
  136. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
  137. package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
  138. package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
  139. package/dist/components/rel-topbar/rel-topbar.ts +0 -446
  140. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
  141. package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
  142. package/dist/components/rel-upload/rel-upload.ts +0 -943
@@ -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 6px linear bar or a 48px circular ring, each either determinate (fills to `value`, 0–100) or indeterminate (loops an animation while you wait). Use `linear-determinate` / `circular-determinate` for uploads, imports and multi-step flows where progress is known, and the `*-indeterminate` modes as a loading signal while a request is in flight. It is a passive `role="progressbar"` element — it does not block interaction (use `rel-modal` with `loading` for that) and does not announce completion on its own.
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 6px bar, `circular-*` a 48px 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 6px tall with a square radius and stretch to the host's width (host is `display: block`). Circular rings are fixed at 48px and the host switches to `display: inline-block`, so they sit inline with text.
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,135 +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"` | `"medium"` | Control scale: `"medium"` = 20px circle with body-lg labels; `"small"` = 16px circle with body-md labels (reflected attribute) |
65
- | `selectedValue` | `string \| null` | `null` | Currently selected option value (reflected attribute) |
66
- | `disabled` | `boolean` | `false` | Disables the entire radio group |
67
-
68
- ### RadioOption Interface
69
- ```typescript
70
- interface RadioOption {
71
- value: string; // Unique value for this option
72
- label: string; // Display label
73
- supportingText?: string; // Optional helper text below the label
74
- disabled?: boolean; // Disable individual option
75
- error?: boolean; // Show error state for individual option
76
- }
77
- ```
78
-
79
- ## Events
80
- | Event | Detail | Description |
81
- |-------|--------|-------------|
82
- | `changed` | None (read `selectedValue` property) | Fired when the selected option changes |
83
-
84
- ## Slots
85
- Default slot not used.
86
-
87
- ## CSS Parts
88
- None exposed.
89
-
90
- ## Tokens / CSS Variables
91
-
92
- ### Semantic Tokens
93
- - `--rel-fonts-font-family` - Font family
94
- - `--rel-body-sm-regular-font-size`, `--rel-body-xs-regular-font-size` - Font sizes for label and supporting text
95
- - `--rel-fonts-weight-body-medium` - Font weight for labels
96
-
97
- ### Spacing Tokens
98
- - `--rel-spacing-xs` - Gap between radio options (also the row gap when wrapping horizontally)
99
- - `--rel-spacing-xl` - Column gap between options in `orientation="horizontal"`
100
- - `--rel-spacing-3xs` - Gap within each option (label and supporting text)
101
- - `--rel-spacing-2xs` - Gap between radio circle and label
102
-
103
- ### Color Tokens
104
- - `--rel-text-label` - Radio button border and checked dot color
105
- - `--rel-text-default` - Label text color
106
- - `--rel-text-placeholder` - Supporting text color
107
- - `--rel-border-info` - Focus outline color
108
- - `--rel-text-danger` - Error state border and dot color
109
-
110
- ## Accessibility
111
- - The option list renders with `role="radiogroup"` and `aria-orientation` reflecting the `orientation` prop
112
- - Uses visually hidden native radio inputs for proper screen reader support
113
- - Custom visual radio circle with proper focus indicators
114
- - Focus visible state with outline offset for keyboard navigation
115
- - Label click targets the radio for larger interaction area
116
- - Disabled state communicated through reduced opacity and cursor change
117
- - Proper `name` grouping ensures only one option can be selected
118
-
119
- ### Keyboard Interactions
120
- - `Tab` - Move focus to/from radio group
121
- - `Arrow Up/Down` - Navigate between radio options
122
- - `Space` - Select focused option
123
-
124
- ### Focus Behavior
125
- - Focus outline appears with offset around the custom radio circle
126
- - Uses info border color for high contrast
127
-
128
- ## Notes / Edge Cases
129
- - The `selectedValue` property is reflected as an attribute and can be set programmatically or via HTML
130
- - When `disabled` is true on the component, all options are disabled regardless of individual option settings
131
- - Individual options can be disabled via the `disabled` property in the `RadioOption` object
132
- - The `error` property on `RadioOption` changes the radio button color to red but does not prevent selection
133
- - Supporting text is optional and appears below the label with left margin alignment
134
- - Native radio inputs are visually hidden but maintain all accessibility features
135
- - 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"` | `"medium"` | Control scale (Figma Size). `"medium"` (default, Figma M) = 20px control with body-lg labels; `"large"` (Figma L) = 24px control with body-xl labels; `"small"` (Figma S) = 20px control with body-md labels — S and M share the control size in the spec, only typography steps down. Reflected. |
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`).