Accessible Toggle Buttons
Toggle buttons are a pleasant interface for toggling a value between two states, and offer the same semantics and keyboard navigation as native checkbox elements.
Example
Checked Toggle Buttons
Unchecked Toggle Buttons
Disabled Toggle Buttons
Markup (HTML)
<div class="toggle">
<input type="checkbox" id="checked" class="toggle-input" checked />
<label for="checked" role="checkbox" aria-checked="true" tabindex="0" aria-labelledby="checked" class="toggle-label"></label>
<span class="toggle-text">Checked Toggle Buttons</span>
</div>
<div class="toggle">
<input type="checkbox" id="unchecked" class="toggle-input" />
<label for="unchecked" role="checkbox" aria-checked="false" tabindex="0" aria-labelledby="unchecked" class="toggle-label"></label>
<span class="toggle-text">Unchecked Toggle Buttons</span>
</div>
<div class="toggle">
<input type="checkbox" id="unchecked_disabled" class="toggle-input" disabled />
<label for="unchecked_disabled" role="checkbox" aria-checked="false" tabindex="0" aria-labelledby="unchecked_disabled" class="toggle-label"></label>
<span class="toggle-text">Disabled Toggle Buttons</span>
</div>
Styles (CSS)
.toggle {
--color-1: #eceeef;
--color-2: #777f86;
--color-3: #313436;
--color-4: #E2001A;
--color-5: #fff6f6;
margin-block: 20px;
display: flex;
}
.toggle .toggle-label {
position: relative;
display: block;
height: 20px;
width: 40px;
background: var(--color-1);
border-radius: 100px;
cursor: pointer;
transition: all 300ms ease;
}
.toggle .toggle-label::after {
position: absolute;
left: 0px;
top: 0px;
display: block;
width: 20px;
height: 20px;
border-radius: 100px;
background: var(--color-2);
box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.05);
content: "";
transition: all 300ms ease;
}
.toggle .toggle-label:active:after {
transform: scale(1.15, 0.85);
}
.toggle .toggle-text {
margin-left: 10px;
font-size: 1rem;
line-height: 20px;
color: var(--color-3);
}
.toggle:has(.toggle-input:disabled) {
cursor: not-allowed !important;
}
.toggle .toggle-input {
display: none;
}
.toggle .toggle-input:checked ~ label {
background: var(--color-1);
}
.toggle .toggle-input:checked ~ label:after {
left: 20px;
background: var(--color-4);
}
.toggle .toggle-input:disabled ~ label {
background: var(--color-1);
pointer-events: none;
}
.toggle .toggle-input:disabled ~ label:after {
background: var(--color-5);
cursor: not-allowed;
}
Functionality (JS)
const toggles = document.querySelectorAll('.toggle');
let label, input;
toggles.forEach((toggle) => {
toggle.addEventListener('keydown', handler, false);
toggle.children[1].addEventListener('click', handler, false);
});
function handler(e) {
label = e.srcElement;
input = label.previousElementSibling;
if(e.type == 'keydown' && e.keyCode == 32 && input.disabled == false || e.type == 'click' && input.disabled == false) {
e.preventDefault();
if (input.checked == true) {
label.setAttribute('aria-checked', 'false');
label.setAttribute('aria-labelledby', 'unchecked');
input.checked = false;
} else {
label.setAttribute('aria-checked', 'true');
label.setAttribute('aria-labelledby', 'checked');
input.checked = true;
}
}
}