Accessible Radio Button Replacements

Using screenreader-only hiding of the original elements they are still keyboard accessible but are visually replaced with content in their wrapper la

by _sir

HTML

<p>
Using screenreader-only hiding of the original elements leaves the radio buttons keyboard accessible but visually replaced with content in their wrapper label, allowing for a clickable, keyboard navigable, and screenreader-friendly interface.
</p>

<form action="" method="POST">
  <div class="color-swatches">
    <label><input type="radio" name="foo" value="1" class="color sr-only"><div class="swatch" style="background: orange;"></div><div class="labelText">Orange</div></label>
    <label><input type="radio" name="foo" value="2" class="color sr-only"><div class="swatch" style="background: black;"></div> <div class="labelText">Black</div></label>
    <label><input type="radio" name="foo" value="3" class="color sr-only"><div class="swatch" style="background: red;"></div> <div class="labelText">Red</div></label>
    <label><input type="radio" name="foo" value="4" class="color sr-only"><div class="swatch" style="background: green;"></div> <div class="labelText">Green</div></label>
  </div>
  <input type="text" name="bar" id="bar">
</form>

CSS

.sr-only {
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

.color-swatches {
  position: relative;
}

.color-swatches label {
  display: inline-block;
  padding-top: 1.5em;
}

.color-swatches .labelText {
  visibility: hidden;
  position: absolute;
  top: 0;
  left: 0;
}

div.swatch {
  width: 20px;
  height: 20px;
  border: 1px solid gray;
  margin: 5px;
}

input:checked + div.swatch {
  border: 1px solid white;
  outline: 2px solid gray;
}

input:checked ~ .labelText {
  visibility: visible !important;
}

/*
 * Native Focus Mimic logic from
 * https://ghinda.net/article/mimic-native-focus-css/
 */
input:focus + div.swatch {
  border: 1px solid white;
  outline-width: 2px;
  outline-style: solid;
  outline-color: Highlight;
}

@media (-webkit-min-device-pixel-ratio:0) {
  input:focus + div.swatch {
    outline-color: -webkit-focus-ring-color;
    outline-style: auto;
  }
}

JavaScript

$('input').on('click', function() {
  $('#bar').val(this.value);
});