Created
December 19, 2024 02:30
-
-
Save stamminator/98da95eb34b50d8d1cfee56d61abfe5e to your computer and use it in GitHub Desktop.
Demo of a simple two-state toggle button
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--default-text: "State 1 (root)"; | |
--toggled-text: "State 2 (root)"; | |
} | |
button { | |
text-align: center; | |
vertical-align: middle; | |
} | |
.btn { | |
display: inline-block; | |
border: 2px outset transparent; | |
padding: 6px 12px; | |
font-family: sans-serif; | |
border-radius: 2px; | |
color: #fff; | |
background-color: #474141; | |
border-color: #3a3636; | |
&:hover { | |
background-color: #3a3636; | |
} | |
&:active { | |
border-style: inset; | |
background-color: #2d2828; | |
} | |
} | |
.btn-toggle { | |
display: inline-flex; | |
flex-wrap: wrap; | |
width: min-content; | |
> * { | |
flex-grow: 1; | |
white-space: nowrap; | |
overflow-y: hidden; | |
} | |
> :first-child { | |
height: auto; | |
} | |
> :nth-child(2) { | |
height: 0; | |
} | |
&.toggled { | |
flex-wrap: wrap-reverse; /* preserves "first baseline" behavior */ | |
> :first-child { | |
height: 0; | |
} | |
> :last-child { | |
height: auto; | |
} | |
} | |
} | |
.btn-toggle-css { | |
display: inline-flex; | |
flex-wrap: wrap; | |
width: min-content; | |
> * { | |
display: none; /* ensure only pseudoelements appear */ | |
} | |
&::before, | |
&::after { | |
flex-grow: 1; | |
white-space: nowrap; | |
overflow-y: hidden; | |
} | |
&::before { | |
height: auto; | |
content: var(--default-text); | |
} | |
&::after { | |
height: 0; | |
content: var(--toggled-text); | |
} | |
&.toggled { | |
flex-wrap: wrap-reverse; /* preserves "first baseline" behavior */ | |
&::before { | |
height: 0; | |
} | |
&::after { | |
height: auto; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div style="display: inline-grid; grid-template-columns: auto auto; gap: 6px; align-items: first baseline;"> | |
<div>Simple toggle</div> | |
<button type="button" class="btn btn-toggle" onclick="this.classList.toggle('toggled')"> | |
<span>Untoggled state</span> | |
<span>Toggled state</span> | |
</button> | |
<div>Pure CSS</div> | |
<button type="button" class="btn btn-toggle-css" onclick="this.classList.toggle('toggled')"></button> | |
<div>Pure CSS with overrides</div> | |
<button type="button" class="btn btn-toggle-css" onclick="this.classList.toggle('toggled')" | |
style="--toggled-text: 'State 2 (element)'"> | |
<span>oops</span> | |
</button> | |
<div>Toggle with accessibility</div> | |
<div> | |
<button type="button" class="btn btn-toggle" onclick="toggleButton(this)"> | |
<span>Accessible label (default)</span> | |
<span>Accessible label (toggled)</span> | |
</button> | |
</div> | |
<div>Advanced toggle<br/>(set state externally)</div> | |
<div> | |
<button id="advancedToggleButton" type="button" class="btn btn-toggle" onclick="toggleButton(this, selectOrUnselectAll)"> | |
<span>Select all</span> | |
<span>Unselect all</span> | |
</button> | |
<div> | |
<label><input type="checkbox" name="checkboxes" value="1" checked /> Box 1</label> | |
<label><input type="checkbox" name="checkboxes" value="2" /> Box 2</label> | |
<label><input type="checkbox" name="checkboxes" value="3" /> Box 3</label> | |
</div> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
/** | |
@param {HTMLButtonElement} btnEl | |
@param {function(HTMLButtonElement=):boolean=} toggledStateFn | |
If provided, rather than the button's state being reversed, it will | |
be set to the result of this function regardless of its current state. | |
*/ | |
function toggleButton(btnEl, toggledStateFn) { | |
/**@type {boolean} */ let isNewStateToggled; | |
if (toggledStateFn) | |
isNewStateToggled = toggledStateFn(btnEl); | |
else | |
isNewStateToggled = !btnEl.classList.contains('toggled'); | |
if (isNewStateToggled) { | |
btnEl.classList.add('toggled'); | |
btnEl.firstElementChild.setAttribute('aria-hidden', 'true'); | |
btnEl.lastElementChild.setAttribute('aria-hidden', 'false'); | |
} | |
else { | |
btnEl.classList.remove('toggled'); | |
btnEl.firstElementChild.setAttribute('aria-hidden', 'false'); | |
btnEl.lastElementChild.setAttribute('aria-hidden', 'true'); | |
} | |
} | |
function synchronizeAdvancedToggleButtonState() { | |
toggleButton(document.getElementById("advancedToggleButton"), () => { | |
// Are all checkboxes checked? | |
let checkboxes = document.getElementsByName("checkboxes"); | |
let allChecked = Array.from(checkboxes).every(x => x.checked === true); | |
return allChecked; | |
}); | |
} | |
/** @param {HTMLButtonElement} btnEl */ | |
function selectOrUnselectAll(btnEl) { | |
let isCurrentStateToggled = btnEl.classList.contains('toggled'); | |
Array.from(document.getElementsByName("checkboxes")).forEach(x => x.checked = !isCurrentStateToggled); | |
return !isCurrentStateToggled; | |
} | |
document.getElementsByName("checkboxes").forEach(x => { | |
x.addEventListener('change', synchronizeAdvancedToggleButtonState); | |
}); | |
// Initialize advancedToggleButton's state so it's in sync with the checkboxes | |
// and so that we don't have to wait until it's pressed to set the ARIA attributes. | |
synchronizeAdvancedToggleButtonState(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Two-state Toggle Button | |
description: Demo of a two-state toggle button that maintains the same width even as the inner text changes, without using JavaScript or hard-coded widths. With a bit of flexbox magic, the width of the button is derived from whichever of the two states' text is wider. | |
authors: | |
- Jacob Stamm | |
resources: | |
normalize_css: no | |
wrap: l | |
panel_js: 0 | |
panel_css: 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment