|
| 1 | +# Guideline |
| 2 | + |
| 3 | +## Intro 👈🤖 |
| 4 | + |
| 5 | +A radio button enables users to select exactly one option from a predefined set of mutually exclusive choices. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Definition |
| 10 | + |
| 11 | +A radio button is a user interface element that allows users to select a single option from a set of mutually exclusive choices, typically displayed as a circular input with a label that becomes filled when selected. |
| 12 | + |
| 13 | +This component family is available in two variants: |
| 14 | +• **Radio-button:** In this template, the component does not display any text or icon. This layout provides greater flexibility when creating other components that require a radio-button to be displayed. |
| 15 | +• **Radio-button item:** In this template, the component displays multiple additional text elements and icon assets. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Best for 👈🤔 |
| 20 | + |
| 21 | +✅ Single selection from a small set of 2–7 mutually exclusive options |
| 22 | + |
| 23 | +✅ Settings or preferences where only one choice applies at a time |
| 24 | + |
| 25 | +✅ Forms requiring explicit user commitment to one option |
| 26 | + |
| 27 | +✅ Yes/No or binary choice questions on a page |
| 28 | + |
| 29 | +✅ Filter controls where one category must be active |
| 30 | + |
| 31 | +✅ Survey questions with predefined answer choices |
| 32 | + |
| 33 | +✅ Configuration panels with exclusive mode selections |
| 34 | + |
| 35 | +✅ Checkout flows for shipping or payment method selection |
| 36 | + |
| 37 | +✅ User onboarding where one path must be chosen |
| 38 | + |
| 39 | +✅ Legal or compliance forms requiring explicit single selection |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## Anatomy 👈🤖 |
| 44 | + |
| 45 | +| # | Element | Purpose | Optional | |
| 46 | +|---|---------|---------|----------| |
| 47 | +| 1 | Radio indicator | Circular input showing selected/unselected state via fill or empty circle | N | |
| 48 | +| 2 | Label | Primary text describing the selectable option | N | |
| 49 | +| 3 | Extra label | Strong accompanying text providing additional emphasis for the label | Y | |
| 50 | +| 4 | Description | Supporting text providing context or clarification for the option | Y | |
| 51 | +| 5 | Icon | Visual element to enhance recognition or categorization of the option | Y | |
| 52 | +| 6 | Error message | Text displayed when validation fails, explaining the error condition | Y | |
| 53 | +| 7 | Divider | Horizontal line separating radio button items in a list | Y | |
| 54 | +| 8 | Container (combined) | Interactive area encompassing indicator and label for click/tap targets; includes outlined variant for emphasis | N | |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## Selected |
| 59 | + |
| 60 | +Typically, a radio button has two main states: Selected and Unselected. |
| 61 | + |
| 62 | +**`False`** The radio button is unselected. Used by default or when the user chooses another option in the group. |
| 63 | + |
| 64 | +**`True`** The radio button is selected. Indicates the user's current active choice within the group. |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## selected_do_&_dont 👈🤔 |
| 69 | + |
| 70 | +✅ **Do:** Pre-select a default option when a recommended or most common choice exists to reduce user effort |
| 71 | +❌ **Don't:** Pre-select an option that could commit users to unwanted actions like subscriptions or agreements |
| 72 | + |
| 73 | +✅ **Do:** Ensure the selected state is visually distinct with a filled indicator and sufficient color contrast |
| 74 | +❌ **Don't:** Use subtle visual differences that make it difficult to distinguish selected from unselected states |
| 75 | + |
| 76 | +✅ **Do:** Allow users to change their selection freely before form submission |
| 77 | +❌ **Don't:** Lock selections or require page refresh to change a radio button choice |
| 78 | + |
| 79 | +✅ **Do:** Provide a "None" or "Not applicable" option if no selection should be a valid user choice |
| 80 | +❌ **Don't:** Force users to select an option when the question may not apply to them |
| 81 | + |
| 82 | +✅ **Do:** Automatically deselect the previous option when a new one is selected within the group |
| 83 | +❌ **Don't:** Allow multiple radio buttons in the same group to be selected simultaneously |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +## Error |
| 88 | + |
| 89 | +**`False`** The field is required but not selected. Example: the "I accept the terms" checkbox is not checked — user action is required. |
| 90 | + |
| 91 | +**`True`** The field is selected but still invalid. Example: the user selects "Subscribe to newsletter" but doesn't provide an email — logical condition not met. |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## error_do_&_dont 👈🤔 |
| 96 | + |
| 97 | +✅ **Do:** Display error messages directly below the radio group with clear, actionable language like "Select an option" |
| 98 | +❌ **Don't:** Use generic error messages like "Invalid input" that don't explain what action is required |
| 99 | + |
| 100 | +✅ **Do:** Use a distinct error color (typically red) for the indicator border and error message text |
| 101 | +❌ **Don't:** Rely solely on color to indicate errors—include text and icons for accessibility |
| 102 | + |
| 103 | +✅ **Do:** Associate error messages programmatically with the radio group using `aria-describedby` |
| 104 | +❌ **Don't:** Place error messages far from the radio group where users may miss the connection |
| 105 | + |
| 106 | +✅ **Do:** Clear error states immediately when the user makes a valid selection |
| 107 | +❌ **Don't:** Keep error indicators visible after the user has corrected the issue |
| 108 | + |
| 109 | +✅ **Do:** Validate on blur or form submission rather than on every interaction to avoid premature errors |
| 110 | +❌ **Don't:** Show error states before the user has had a chance to interact with the radio group |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Outlined |
| 115 | + |
| 116 | +**`False`** This is the default layout of the component. |
| 117 | + |
| 118 | +**`True`** Outlined radio buttons are designed to stand out and draw the user's attention. They are often used to emphasize significant or impactful options that require careful consideration in the interface. |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## outlined_do_&_dont 👈🤔 |
| 123 | + |
| 124 | +✅ **Do:** Use outlined variants sparingly for high-importance decisions like pricing plans or subscription tiers |
| 125 | +❌ **Don't:** Apply outlined styling to every radio group, which diminishes its emphasis effect |
| 126 | + |
| 127 | +✅ **Do:** Maintain consistent padding and border styling across all outlined radio buttons in a group |
| 128 | +❌ **Don't:** Mix outlined and non-outlined radio buttons within the same selection group |
| 129 | + |
| 130 | +✅ **Do:** Ensure outlined borders have sufficient contrast against the background (minimum 3:1 ratio) |
| 131 | +❌ **Don't:** Use light gray borders on white backgrounds that fail accessibility contrast requirements |
| 132 | + |
| 133 | +✅ **Do:** Increase touch target size when using outlined variants to accommodate the larger visual footprint |
| 134 | +❌ **Don't:** Reduce clickable areas to match only the visible border boundaries |
| 135 | + |
| 136 | +✅ **Do:** Use outlined variants when radio options contain rich content like descriptions or icons |
| 137 | +❌ **Don't:** Use outlined variants for simple binary choices where standard radio buttons suffice |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Reverse |
| 142 | + |
| 143 | +**`False`** This is the default layout of the component. From left to right, the order of the elements is as follows: radio button / text / icon. |
| 144 | + |
| 145 | +**`True`** As its name suggests, this layout is the reversed mirror of the "Default" template. From left to right, the order of the elements is as follows: icon / text / radio button. This variant is necessary for RTL mode and certain mobile use cases. |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +## reverse_do_&_dont 👈🤔 |
| 150 | + |
| 151 | +✅ **Do:** Use reverse layout for right-to-left (RTL) languages like Arabic and Hebrew |
| 152 | +❌ **Don't:** Use reverse layout arbitrarily without considering reading direction conventions |
| 153 | + |
| 154 | +✅ **Do:** Apply reverse layout consistently across all radio buttons when supporting RTL interfaces |
| 155 | +❌ **Don't:** Mix LTR and RTL radio button layouts on the same page or within the same form |
| 156 | + |
| 157 | +✅ **Do:** Position the radio indicator on the trailing edge (right side for LTR, left for RTL) for mobile list interfaces |
| 158 | +❌ **Don't:** Place indicators in inconsistent positions across different screen sizes |
| 159 | + |
| 160 | +✅ **Do:** Ensure tab order and focus management work correctly regardless of visual layout direction |
| 161 | +❌ **Don't:** Assume visual order equals DOM order—verify keyboard navigation flows logically |
| 162 | + |
| 163 | +✅ **Do:** Test reverse layouts with screen readers to confirm proper announcement order |
| 164 | +❌ **Don't:** Rely solely on visual testing when implementing layout variants |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## Other boolean options |
| 169 | + |
| 170 | +**`Extra label`** It is possible to display or hide strong accompanying text for the main label. |
| 171 | + |
| 172 | +**`Description`** It is possible to display or hide accompanying text for the main label. |
| 173 | + |
| 174 | +**`Icon`** It is possible to display or hide an icon. If displayed, this option includes functionality to choose any Solaris icon. |
| 175 | + |
| 176 | +**`Divider`** It is possible to display or hide a dividing element (line). |
| 177 | + |
| 178 | +**`Error message`** In the context where the component is in its "Error" true option, the error message can be displayed. |
| 179 | + |
| 180 | +--- |
| 181 | + |
| 182 | +## other_boolean_options_do_&_dont 👈🤔 |
| 183 | + |
| 184 | +✅ **Do:** Use descriptions to provide helpful context that aids decision-making between options |
| 185 | +❌ **Don't:** Add descriptions to every radio button when options are already self-explanatory |
| 186 | + |
| 187 | +✅ **Do:** Use icons that clearly represent the option and are recognizable at small sizes |
| 188 | +❌ **Don't:** Use decorative icons that don't add meaningful information to the selection |
| 189 | + |
| 190 | +✅ **Do:** Use dividers to create visual separation in long lists of radio button items |
| 191 | +❌ **Don't:** Add dividers to groups with only 2–3 options where separation isn't needed |
| 192 | + |
| 193 | +✅ **Do:** Keep extra labels concise and complementary to the main label text |
| 194 | +❌ **Don't:** Duplicate information between the main label and extra label |
| 195 | + |
| 196 | +✅ **Do:** Show error messages only when validation has failed and hide them upon correction |
| 197 | +❌ **Don't:** Display placeholder error message space when no error exists |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +# Specs |
| 202 | + |
| 203 | +## States |
| 204 | + |
| 205 | +**`Enabled`** The default active state where the radio button is functional and selectable. It may show an unselected or selected style, with a label and helper text visible. |
| 206 | + |
| 207 | +**`Hover`** When a user places a pointing device over a radio button, but has not yet taken action on it. This includes a subtle visual indicator (highlighted background or color change) to show interactivity. |
| 208 | + |
| 209 | +**`Focus`** When a user selects a radio button via keyboard or voice command, but has not yet taken action on it. Mirrors the "Hover" state but includes an additional border. |
| 210 | + |
| 211 | +**`Pressed`** An intermediate state that communicates a user has taken action on a radio button, and that it is in the process of navigating to a destination, triggering logic, or transmitting data. |
| 212 | + |
| 213 | +**`Read only`** The radio button is displayed in a specific state (selected or unselected), but the user cannot modify it with a label and helper text visible. |
| 214 | + |
| 215 | +**`Disabled`** The radio button is non-interactive and grayed out to indicate it cannot be selected or changed. The label and helper text are muted. |
| 216 | + |
| 217 | +**`Skeleton`** Improves the perceived loading time by providing a visual cue of where radio button will appear once fully loaded. Uses the "Skeleton" component, variant "Security marge=True". |
| 218 | + |
| 219 | +--- |
| 220 | + |
| 221 | +## Layout and spacing |
| 222 | + |
| 223 | +🚧 Content to be added |
| 224 | + |
| 225 | +--- |
| 226 | + |
| 227 | +# Accessibility 👈🤖 |
| 228 | + |
| 229 | +## Accessibility intro |
| 230 | + |
| 231 | +Radio buttons must meet WCAG 2.2 Level AA standards, ensuring keyboard operability, proper labeling, and clear state communication for all users including those using assistive technologies. For comprehensive accessibility guidance, see the [Orange Unified Design System Accessibility Overview](https://unified-design-system.orange.com/472794e18/p/88ebab-accessibility-and-sustainability). |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +## Accessibility Challenges |
| 236 | + |
| 237 | +Radio buttons present unique accessibility challenges because they operate as groups where only one option can be selected, requiring proper grouping semantics, clear state indication, and coordinated keyboard navigation across multiple interactive elements. |
| 238 | + |
| 239 | +### Key Challenges |
| 240 | +- Communicating mutual exclusivity to screen reader users who cannot see visual relationships |
| 241 | +- Managing roving tabindex for efficient keyboard navigation within groups |
| 242 | +- Ensuring error states are announced and associated with the correct group |
| 243 | +- Maintaining visible focus indicators across all interaction states |
| 244 | + |
| 245 | +### Critical Success Factors |
| 246 | +1. Group radio buttons using `<fieldset>` with `<legend>` or `role="radiogroup"` with `aria-labelledby` (WCAG 1.3.1) |
| 247 | +2. Ensure each radio button has an accessible name via `<label>` or `aria-label` (WCAG 4.1.2) |
| 248 | +3. Provide visible focus indicators with ≥3:1 contrast ratio (WCAG 2.4.7) |
| 249 | +4. Associate error messages with the group using `aria-describedby` (WCAG 3.3.1) |
| 250 | + |
| 251 | +--- |
| 252 | + |
| 253 | +## Design Requirements |
| 254 | + |
| 255 | +### Structure & Labels |
| 256 | +- [ ] **Group labeling**: Use `<fieldset>` and `<legend>` to provide group context ([Orange label guidelines](https://a11y-guidelines.orange.com/en/web/components-examples/)) |
| 257 | +- [ ] **Individual labels**: Every radio button has a visible `<label>` associated via `for`/`id` |
| 258 | +- [ ] **Required indication**: Mark required groups with "(required)" in legend, use `aria-required="true"` |
| 259 | + |
| 260 | +### Visual Design |
| 261 | +- [ ] **Focus indicator**: 3:1 minimum contrast, visible on all backgrounds ([Focus guidelines](https://a11y-guidelines.orange.com/en/web/design/focus-visible/)) |
| 262 | +- [ ] **State contrast**: Selected/unselected states distinguishable without relying on color alone |
| 263 | +- [ ] **Touch target**: Minimum 44×44px interactive area for radio button and label combined |
| 264 | + |
| 265 | +### Content |
| 266 | +- [ ] **Error messages**: ❌ "Error" / ✅ "Select your preferred contact method" ([Error guidelines](https://a11y-guidelines.orange.com/en/web/components-examples/forms/)) |
| 267 | +- [ ] **Concise labels**: Use clear, scannable option text under 3–4 words per label |
| 268 | + |
| 269 | +--- |
| 270 | + |
| 271 | +## Testing Checklist |
| 272 | + |
| 273 | +### Screen Reader Testing |
| 274 | +- [ ] Test with NVDA (Windows), JAWS (Windows), VoiceOver (macOS/iOS), TalkBack (Android) |
| 275 | +- [ ] Verify group label announced, option labels read, selected state communicated, error messages associated |
| 276 | + |
| 277 | +### Keyboard Testing |
| 278 | +- [ ] Tab moves focus to group, Arrow keys navigate between options, Space selects focused option |
| 279 | +- [ ] Verify focus indicator visible with ≥3:1 contrast throughout all states |
| 280 | + |
| 281 | +### Visual Testing |
| 282 | +- [ ] Confirm selected/unselected states distinguishable by shape, not just color |
| 283 | +- [ ] Verify error indicators include icon or text in addition to color change |
| 284 | + |
| 285 | +Resources: [Orange Accessibility Testing Guide](https://a11y-guidelines.orange.com/en/web/toolbox/) |
| 286 | + |
| 287 | +--- |
| 288 | + |
| 289 | +## Key WCAG Criteria |
| 290 | + |
| 291 | +- **1.3.1 Info and Relationships** (A): Radio groups use proper `<fieldset>`/`<legend>` or ARIA grouping semantics |
| 292 | +- **2.1.1 Keyboard** (A): All radio buttons operable via Tab and Arrow keys without mouse |
| 293 | +- **2.4.7 Focus Visible** (AA): Visible focus indicator on active radio button with ≥3:1 contrast |
| 294 | +- **3.3.1 Error Identification** (A): Errors identified in text and associated with radio group via `aria-describedby` |
| 295 | +- **4.1.2 Name, Role, Value** (A): Radio button role, checked state, and accessible name exposed to assistive technology |
| 296 | + |
| 297 | +For complete reference: [Orange Accessibility Guidelines - Form Components](https://a11y-guidelines.orange.com/en/web/components-examples/forms/) |
| 298 | + |
| 299 | +--- |
| 300 | + |
| 301 | +## Additional Resources |
| 302 | + |
| 303 | +- [Orange Accessibility Guidelines - Radio Buttons](https://a11y-guidelines.orange.com/en/web/components-examples/) |
| 304 | +- [WCAG 2.2 Understanding 1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships) |
| 305 | +- [WAI-ARIA Radio Group Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) |
| 306 | +- [Orange Design System - Accessibility & Sustainability](https://unified-design-system.orange.com/472794e18/p/88ebab-accessibility-and-sustainability) |
| 307 | + |
| 308 | +--- |
| 309 | + |
| 310 | +# Changelog |
| 311 | + |
| 312 | +| Date | Number | Notes | Designer | |
| 313 | +|------|--------|-------|----------| |
| 314 | +| Nov 7, 2025 | 1.4.0 | • A new Read-only variant has been added for the .Radiobutton.Indicator component, supporting two boolean variants — Selected = True/False. This variant introduces two new color tokens: | Anton Astafev | |
| 315 | +| | | • ouds/color/action/read-only-primary — used for the indicator (shape) | | |
| 316 | +| | | • ouds/color/action/read-only-secondary — used for the stroke | | |
| 317 | +| | | • The new Read-only variant has been integrated into the Read-only variant of both the Radio button and Radio button Item components. | | |
| 318 | +| | | • We replaced the token in Error text container ouds-control-text-input-space-padding-block-top-helper-text with ouds-control-control-item-space-padding-block-top-error-text. | | |
| 319 | +| | | • "Helper text" is now called "Description". | | |
| 320 | +| | | • "Additional label" is now called "Extra label". | | |
| 321 | +| Oct 20, 2025 | 1.3.0 | • The Radio button item has been split into two boolean variants: → Error = True/False → Selected = True/False | Anton Astafev | |
| 322 | +| | | • The divider color is now functional in the Error state — it changes dynamically according to the component status. | | |
| 323 | +| | | • The icon in the Error state is fixed to .Component/alert/important; its color changes together with the divider depending on the component's status. → The new token $control-control-item-size-error-icon is used for the icon size. → The new token $control-text-input-space-padding-inline-error-icon is used for the error icon container. 🆕 Both tokens are now available in the latest release of the Token Library 2.1.0. | | |
| 324 | +| | | • Added Error text (from the Input component) — it follows the same padding-inline as control-item (16px) and uses → $control-text-input-space-padding-block-top-helper-text for block padding. By default, the Error text adapts automatically to match the component status: → Selected → displays the corresponding default error message for the selected state. → Unselected → displays the corresponding default error message for the unselected state. | | |
| 325 | +| | | • The "Read only" state has been updated to replace control items (in their disabled states) — both selected and unselected — with the Tag → Text only → Muted component: → Positive with label "Selected" if selected = True → Negative with label "Unselected" if selected = False | | |
| 326 | +| | | • Harmonisation of spacing across the control-item family We've unified sizing tokens across the entire control-item family (previously they were defined per component) to align spacing with other control items such as Text input. Replacement note: instead of the single token padding inset 12, use the following tokens: → ouds/_control/control-item/space/padding-inline → 16 → ouds/_control/control-item/space/padding-block → 12 Additionally, for the control-item family: → ouds/_control/control-item/space/column-gap → 12 → ouds/_control/control-item/size/max-width → 480 | | |
| 327 | +| Sep 19, 2025 | 1.2.0 | • In the initial settings, the 'Divider' variant is now hidden. | Maxime Tonnerre | |
| 328 | +| Jul 21, 2025 | 1.1.0 | • The name of the family to which this component belongs is changing: Input → Control. As a result, the token naming convention is being updated. | Maxime Tonnerre | |
| 329 | +| | | • Following the renaming of the 'Control' category, the name of the token sub-family 'control-item' is now becoming 'item'." | | |
| 330 | +| Jan 22, 2025 | 1.0.0 | • Component creation | Maxime Tonnerre | |
0 commit comments