`.
219 |
220 | ### Underlying DOM Structure
221 |
222 | For reference, the underlying HTML DOM structure are all `div`s and looks as follows.
223 |
224 | ```html
225 |
226 |
233 | Red
234 |
235 |
242 | Green
243 |
244 |
251 | Blue
252 |
253 |
254 | ```
255 |
256 | ### Overriding Styles
257 |
258 | These are the default styles. Copy and paste the following into your app to customize them.
259 |
260 | ```css
261 | [data-palmerhq-radio-group] {
262 | padding: 0;
263 | margin: 0;
264 | list-style: none;
265 | }
266 |
267 | [data-palmerhq-radio-group]:focus {
268 | outline: none;
269 | }
270 |
271 | [data-palmerhq-radio] {
272 | border: 2px solid transparent;
273 | border-radius: 5px;
274 | display: inline-block;
275 | position: relative;
276 | padding: 0.125em;
277 | padding-left: 1.5em;
278 | padding-right: 0.5em;
279 | cursor: default;
280 | outline: none;
281 | }
282 |
283 | [data-palmerhq-radio] + [data-palmerhq-radio] {
284 | margin-left: 1em;
285 | }
286 |
287 | [data-palmerhq-radio]::before,
288 | [data-palmerhq-radio]::after {
289 | position: absolute;
290 | top: 50%;
291 | left: 7px;
292 | transform: translate(-20%, -50%);
293 | content: '';
294 | }
295 |
296 | [data-palmerhq-radio]::before {
297 | width: 14px;
298 | height: 14px;
299 | border: 1px solid hsl(0, 0%, 66%);
300 | border-radius: 100%;
301 | background-image: linear-gradient(to bottom, hsl(300, 3%, 93%), #fff 60%);
302 | }
303 |
304 | [data-palmerhq-radio]:active::before {
305 | background-image: linear-gradient(
306 | to bottom,
307 | hsl(300, 3%, 73%),
308 | hsl(300, 3%, 93%)
309 | );
310 | }
311 |
312 | [data-palmerhq-radio][aria-checked='true']::before {
313 | border-color: hsl(216, 80%, 50%);
314 | background: hsl(217, 95%, 68%);
315 | background-image: linear-gradient(
316 | to bottom,
317 | hsl(217, 95%, 68%),
318 | hsl(216, 80%, 57%)
319 | );
320 | }
321 |
322 | [data-palmerhq-radio][aria-checked='true']::after {
323 | display: block;
324 | border: 0.1875em solid #fff;
325 | border-radius: 100%;
326 | transform: translate(25%, -50%);
327 | }
328 |
329 | [data-palmerhq-radio][aria-checked='mixed']:active::before,
330 | [data-palmerhq-radio][aria-checked='true']:active::before {
331 | background-image: linear-gradient(
332 | to bottom,
333 | hsl(216, 80%, 57%),
334 | hsl(217, 95%, 68%) 60%
335 | );
336 | }
337 |
338 | [data-palmerhq-radio]:hover::before {
339 | border-color: hsl(216, 94%, 65%);
340 | }
341 |
342 | [data-palmerhq-radio][data-palmerhq-radio-focus='true'] {
343 | border-color: hsl(216, 94%, 73%);
344 | background-color: hsl(216, 80%, 97%);
345 | }
346 |
347 | [data-palmerhq-radio]:hover {
348 | background-color: hsl(216, 80%, 92%);
349 | }
350 | ```
351 |
352 | ## Accessibility Features
353 |
354 | - Uses CSS attribute selectors for synchronizing `aria-checked` state with the visual state indicator.
355 | - Uses CSS `:hover` and `:focus` pseudo-selectors for styling visual keyboard focus and hover.
356 | - Focus indicator encompasses both radio button and label, making it easier to perceive which option is being chosen.
357 | - Hover changes background of both radio button and label, making it easier to perceive that clicking either the label or button will activate the radio button.
358 |
359 |
Keyboard Support
360 |
361 |
362 |
363 |
364 | | Key |
365 | Function |
366 |
367 |
368 |
369 |
370 | | Tab |
371 |
372 |
373 | - Moves focus to the checked
radio button in the radiogroup.
374 | - If a
radio button is not checked, focus moves to the first radio button in the group.
375 |
376 | |
377 |
378 |
379 | | Space |
380 |
381 |
382 | - If the
radio button with focus is not checked, changes the state to checked.
383 | - Otherwise, does nothing.
384 | - Note: The state where a radio is not checked only occurs on page load.
385 |
386 | |
387 |
388 |
389 | | Right arrow |
390 |
391 |
392 | - Moves focus to and checks the next
radio button in the group.
393 | - If focus is on the last
radio button, moves focus to the first radio button.
394 | - The state of the previously checked radio button is changed to unchecked.
395 |
396 | |
397 |
398 |
399 | | Down arrow |
400 |
401 |
402 | - Moves focus to and checks the next
radio button in the group.
403 | - If focus is on the last
radio button, moves focus to the first radio button.
404 | - The state of the previously checked radio button is changed to unchecked.
405 |
406 | |
407 |
408 |
409 | | Left arrow |
410 |
411 |
412 | - Moves focus to and checks the previous
radio button in the group.
413 | - If focus is on the first
radio button, moves focus to and checks the last radio button.
414 | - The state of the previously checked radio button is changed to unchecked.
415 |
416 | |
417 |
418 |
419 | | Up arrow |
420 |
421 |
422 | - Moves focus to and checks the previous
radio button in the group.
423 | - If focus is on the first
radio button, moves focus to and checks the last radio button.
424 | - The state of the previously checked radio button is changed to unchecked.
425 |
426 | |
427 |
428 |
429 |
430 |
431 |
Role, Property, State, and Tabindex Attributes
432 |
433 |
434 |
435 | | Role |
436 | Attributes |
437 | Element |
438 | Usage |
439 |
440 |
441 |
442 |
443 | radiogroup |
444 | |
445 | div |
446 |
447 |
448 | - Identifies the
div element as a container for a group of radio buttons.
449 | - Is not focusable because focus is managed using a roving tabindex strategy as described below.
450 |
451 | |
452 |
453 |
454 | |
455 | aria-labelledby="[IDREF]" |
456 | div |
457 | Refers to the element that contains the label of the radio group. |
458 |
459 |
460 | radio |
461 | |
462 | div |
463 |
464 |
465 | - Identifies the
div element as an ARIA radio button.
466 | - The accessible name is computed from the child text content of the
div element.
467 |
468 | |
469 |
470 |
471 | |
472 | tabindex="-1" |
473 | div |
474 |
475 |
476 | - Makes the element focusable but not part of the page Tab sequence.
477 | - Applied to all radio buttons contained in the radio group except for one that is included in the page Tab sequence.
478 | - This approach to managing focus is described in the section on roving tabindex.
479 |
480 | |
481 |
482 |
483 | |
484 | tabindex="0" |
485 | div |
486 |
487 |
488 | - Makes the radio button focusable and includes it in the page Tab sequence.
489 | - Set on only one radio in the radio group.
490 | - On page load, is set on the first radio button in the radio group.
491 | - Moves with focus inside the radio group so the most recently focused radio button is included in the page Tab sequence.
492 | - This approach to managing focus is described in the section on roving tabindex.
493 |
494 | |
495 |
496 |
497 | |
498 | aria-checked="false" |
499 | div |
500 |
501 |
502 | - Identifies
radio buttons which are not checked.
503 | - CSS attribute selectors (e.g.
[aria-checked="false"]) are used to synchronize the visual states with the value of the aria-checked attribute.
504 | - The CSS
::before pseudo-class is used to indicate visual state of unchecked radio buttons to support high contrast settings in operating systems and browsers.
505 |
506 | |
507 |
508 |
509 | |
510 | aria-checked="true" |
511 | div |
512 |
513 |
514 | - Identifies the
radio button which is checked.
515 | - CSS attribute selectors (e.g.
[aria-checked="true"]) are used to synchronize the visual states with the value of the aria-checked attribute.
516 | - The CSS
::before pseudo-class is used to indicate visual state of checked radio buttons to support high contrast settings in operating systems and browsers.
517 |
518 | |
519 |
520 |
521 |
522 |
523 | ## Authors
524 |
525 | - Jared Palmer ([@jaredpalmer](https://twitter.com/jaredpalmer))
526 |
527 | ---
528 |
529 | > MIT License
530 |
--------------------------------------------------------------------------------