As an experienced full-stack developer, toggle buttons are one of the most ubiquitous and useful UI elements we constantly implement. Over years building web and mobile apps, I‘ve used my share of toggles for sign-in flows, profiles, settings, and any place users need to switch between binary states.
So today, I want to provide a definitive guide to crafting excellent toggles in JavaScript – beyond the basics to pro techniques I use on cutting-edge web projects.
We‘ll cover:
- Do‘s and don‘ts for interface design
- The psychology and ethics
- Accessibility
- Performance optimizations
- State persistence
- Code architecture best practices
- Advanced examples like animated toggles
We have a lot to get through, so let‘s dive right in!
The Psychology of Toggling
We all know toggles switch between on/off or checked/unchecked state. But underneath this simple interaction lives a potent psychological force. The urge to tap and flip switches is irresistible to humans.
"Mini quests provoke your curiosity," writes psychologist B.J. Fogg who studies Persuasive Design. And toggles are the ultimate mini quest, giving the dopamine hit of changing a small thing in the universe.
This delightful feeling toggles provide means users will play with them even when there‘s no purpose. So we must design ethical toggles that empower, not distract.
Toggles also scratch our itch for control. For users attempting complex tasks on unfamiliar UIs, clearly labeled toggles offer the comforting semblance of options and mastery. This contrasts against interfaces lacking perceived agency that negativiely impact emotional wellbeing according to research from Yale‘s School of Management.
So in short…
- Well designed toggles improve UX by:
- Fulfilling users‘ quest for visible impact
- Providing a sense of control
With that deeper motivation in mind, let‘s see how to build user-centered toggles.
Design Best Practices
Follow these best practices for toggle buttons in terms of layout, functionality and copy:
1. Stick to convention – On the left is a green indicator signaling "on". On the right sits "off". This trained schema eliminates cognitive load.
2. Clearly indicate state – Use multiple cues like color (green/red), textual labels ("on"/"off"), characters (checkmark/x), animations and icons to indicate status. Don‘t rely solely on color which excludes color deficient users.
3. Speak the user‘s language – Labels should clearly reflect what is being toggled, not tech jargon. For example "Enable email updates" or "Public profile" communicate the impact.
4. Confirm irreversible actions – Deleting data or destructuve changes warrants an additional confirmation prompt before completing the toggle action.
These evidence-based practices reduce user errors and boost accessibility for diverse users of all abilities.
For specifics, meet WCAG standards with:
- Color contrast ratio of 4.5:1 between text/background
- Minimum target size of 44 x 44px
- Appropriate focus outline style
Now let‘s get hands-on with examples and code!
Anatomy of a Toggle
Before diving into JavaScript, it helps to understand toggle mark-up semantically:
<button class="toggle" aria-pressed="false">
<span class="visually-hidden">Toggle options menu</span>
<svg class="icon" viewBox="0 0 16 16">
<path d="..."></path>
</svg>
</button>
Notable features:
- Native
<button>for semantics, keyboard access, and built-in browser affordances aria-*attributes to communicate state changes to assistive technology<span class="visually-hidden">to provide a text description for screen readers without visual clutter- SVG icon that will toggle between open/close
This robust HTML foundation enables us to layer the JavaScript that drives the actual toggling interaction.
Tracking Toggle State
Before writing any toggle logic, we need to set up state tracking so JavaScript knows whether the toggle is currently on or off.
There are two approaches to managing state:
1. External State Variable
Store toggle state in a simple variable:
// Track toggle state
let toggleState = ‘off‘;
Pros: Simple to implement
Cons: Can get messy if you have many toggles
2. Internal State Attribute
Use a data- attribute right on the toggle element:
<button data-toggled="off">
...
</button>
const el = document.querySelector(‘.toggle‘);
let toggleState = el.dataset.toggled;
Pros: State lives right on the element, self contained component
Cons: You need to parse strings to convert to booleans
Based on scale and context, choose the right state tracking approach. With that foundation handled next comes the toggle logic itself…
JavaScript Toggle Logic
Regardless of state tracking method, the JavaScript follows the same simple conditional logic:
function toggle() {
// Get current state
let toggleState = ‘off‘;
// Toggle state
if(toggleState === ‘off‘) {
toggleState = ‘on‘;
} else {
toggleState = ‘off‘;
}
// Update DOM/UI with new state
updateUI(toggleState);
}
function updateUI(state) {
// Toggle button text
button.textContent = state;
// Toggle color class
button.classList.toggle(‘is-active‘);
}
This declarative approach separates concerns:
- Get current state
- Toggle state value
- Update UI based on new state
It remains easy to read even as more complex view logic piles up in updateUI().
Next let‘s explore techniques for dynamically updating our toggles…
Updating Toggle Button Styles
Now the logic flips between "on" and "off". To finish the component, we animate the style changes in the UI:
Option 1: Toggle a CSS Class
The simplest way to style toggles is adding/removing a class:
function updateUI(state) {
button.classList.toggle(‘is-active‘);
}
.toggle.is-active {
background: green;
color: white;
}
Toggling a class gives complete control over styles without touching the JavaScript.
To animate transitions between states, use CSS properties like transition: background 300ms:
.toggle {
transition: background 300ms linear;
}
.toggle.is-active {
background: green;
}
This seamlessly animates the background color change over 300ms when toggling active state.
Benefits:
- Clean separation of concerns
- Easy transitions and animations
Downsides:
- More complex selectors required for extensive styling tweaks
Option 2: Toggle Properties Directly
For greater control, directly update styles in JavaScript:
function updateUI(state) {
button.style.background = ‘green‘;
button.style.color = ‘white‘;
}
This bypasses classes to set properties dynamically.
To animate, use transitionend events to detect when animations finish:
button.addEventListener(‘transitionend‘, () => {
toggleComplete();
});
function toggleComplete() {
// Animation finished
}
Benefits:
- Granular control over every style property
Downsides:
- Mixes CSS and JavaScript
- More complex animation handling
So in summary, toggling classes keeps concerns separated. But direct properties allows greater customization.
Choose the approach that best suits your project and team.
Persisting Toggle State
Now that styling is handled, let‘s ensure our toggle remembers its on/off state even when the page reloads.
The Web Storage API offers two forms of persistence:
sessionStorage
- Stores data until browser tab is closed
- Use for non-critical transient data
localStorage
- Saves data with no expiration
- Use for important user settings
We want toggles to always remember their state, so localStorage fits the need.
Here‘s how to integrate it:
// On page load
// Get value from localStorage
let storedState = localStorage.getItem(‘toggleState‘);
// If none exists, default to ‘off‘
let toggleState = storedState || ‘off‘;
// Render initial UI with value
updateUI(toggleState);
// On toggle click
function toggle() {
// Toggle state variable
if(toggleState === ‘off‘) {
toggleState = ‘on‘;
} else {
toggleState = ‘off‘;
}
// Save new state to localStorage
localStorage.setItem(‘toggleState‘, toggleState);
// Update UI
updateUI(toggleState);
}
This guarantees the toggle state persists across visits!
For robust apps with many toggles, explore state management libraries like Redux or JSPM. But localStorage offers a simple built-in solution.
Accessible Toggle Buttons
Next let‘s ensure our toggles work for all users.
The W3 Web Accessibility Initiative (WAI) provides guidance for blindness, limited mobility, deafness, and neurological disorders.
Key areas to address:
Focus
- Visually indicate
:focusso sighted keyboard users know current location - Increase click target size for motor difficulties
ARIA Roles
- Use
role="switch"andaria-checkedattributes to communicate state changes to screen readers
Keyboard Access
- React to
Enter/Spacekeypresses for non-mouse use
Color Contrast
- WCAG recommends 4.5:1 contrast between text and background
Building these best practices boosts access for those less considered in mainstream UX.
For detailed code implementation of the above, see Inclusive Components: Toggle Button.
Performant Toggle Buttons
Alongside accessibility, we must optimize performance particularly on mobile devices.
Toggles requiring JavaScript are typically triggered by touch or click events. This leads to potential jank between the physical interaction and visual feedback.
To eliminate jank, replace built-in event handlers with touchstart:
// Bad
button.addEventListener(‘click‘, toggle)
// Good
button.addEventListener(‘touchstart‘, toggle);
Supporting both inputs creates overhead:
function tapHandler(event) {
if(event.type === ‘touchstart‘) {
// Handle touch
} else if(event.type === ‘click‘) {
// Handle mouse
}
}
button.addEventListener(‘touchstart‘, tapHandler);
button.addEventListener(‘click‘, tapHandler);
So when possible, use just touchstart for the fastest, jitter-free response, falling back to click for non-touch devices.
Additionally, every DOM read/write incurs cost. To minimize paints:
❌ Read values repeatedly on each call
// Expensive
function toggle() {
// DOM read on every toggle 👎
let currentState = button.dataset.toggle;
currentState = // ...
}
✅ Cache in memory outside the handler
// Cheap
let currentState;
function toggle() {
// Cache read once
currentState = // ...
}
With state loosely tracked, toggles stay light and nimble.
Advanced Examples
So far we have covered all the toggle fundamentals – now let‘s explore some advanced implementations…
Animated Icon Toggles
For animated toggles using only vector icons:
<button>
<svg class="icon" viewBox="0 0 24 24">
<path class="line1" d="M4 18h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2zM4 6h16v10H4V6z"/>
<path class="line2" d="M8 12h8c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2zM8 6h8v4H8V6z"/>
</svg>
</button>
With JavaScript, toggle active class:
function toggle() {
button.classList.toggle(‘active‘);
}
Finally, use [CSS transforms](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate()) to animate:
/* Rotate line paths by 45 deg on active */
.icon.active .line1 {
transform: rotate(45deg);
}
.icon.active .line2 {
transform: rotate(-45deg);
}
This achieves performant animations without JavaScript, keeping things smooth.
View CodePen demo here.
Toggle Groups
For toggles that are mutually exclusive in groups:
<div class="toggle-group">
<button class="toggle">Option 1</button>
<button class="toggle">Option 2</button>
</div>
Group behavior requires slightly more complex logic:
// Query toggle group
const toggles = document.querySelectorAll(‘.toggle‘);
toggles.forEach(toggle => {
toggle.addEventListener(‘click‘, function() {
// Remove active from all toggles first
toggles.forEach(t => t.classList.remove(‘active‘))
// Then activate the clicked one
toggle.classList.add(‘active‘);
})
})
This enforces only one active toggle at a time.
Try the demo here.
More enhancements might include persisting active toggle or intelligently selecting defaults. But the concept remains the same!
Animated Toggle Transitions
For smooth animated transitions, use CSS transitions on a height change:
.toggle {
height: 0;
overflow: hidden;
/* Animate height change */
transition: height 300ms ease;
}
.toggle.active {
height: 50px; /* Visible height */
}
Now contents will elegantly slide down/up when toggled.
See the technique on CSS Tricks.
Transitions create effortless animations without JavaScript, for 60 FPS smoothness.
Conclusion & Next Steps
Phew, we really covered the landscape of crafting excellent toggles with HTML, CSS, and JS!
Here are some key takeways:
- Design for universal usability with clear visual feedback
- Manage state in a central store like
localStorage - Separate DOM updates from business logic
- Mind transitions for buttery smooth interactions
To take things further:
- Add toggle components to your UI library
- Explore draggable toggles on touch devices
- Integrate 3D toggle animations with libraries like GreenSock
I hope mapping out professional techniques gives a solid starting point for raising your toggle game. Feel free to reach out if any questions pop up applying these concepts!
The details may evolve, but the psychology and ethical impact remain constant over years building web products. Keep users at the heart through clever yet purposeful interactions.
Now get togglin‘ 🙂


