The ability to dynamically add, remove and toggle CSS classes on HTML elements is an extremely useful technique for web developers. It allows us to change styling and behavior without manually editing HTML or CSS code. In this comprehensive guide, we’ll explore the ins and outs of manipulating classes from JavaScript.
Why Dynamic Classes Matter
Before diving into code specifics, it’s worth understanding why the ability to manipulate classes dynamically is so valuable:
Creating interactivity and UI states – Toggling classes allows building complex, interactive interfaces and UI components without much custom CSS code. For example, opening a modal overlay involves temporarily adding a "open" class.
Animations/Transitions – CSS animations are often triggered on class changes. For example, cascading content animation on accordions relies on toggling visibility classes in JS.
Adaptive design – Dynamic classes allow altering styling based on user interaction and application state. This enables use-cases like toggle themes, responsive design, and more.
Accessibility – Semantic classes can communicate non-visual state information for accessibility, like whether an accordion is open or closed for screen readers.
Performance – Applying bulk styling by changing a class is faster than directly manipulating many individual style properties. (More on this later…)
In summary, dynamic classes are integral to building maintainable, interactive websites and web apps. Understanding how to effectively manage classes from JavaScript is a must-have skill.
Accessing Elements
Before adding classes, we need to access the HTML elements we want to manipulate. Here are modern best practices for querying elements from JavaScript:
// Newer Methods ✅
// Get by ID
const modal = document.getElementById(‘modal‘);
// CSS selector
const panels = document.querySelectorAll(‘.panel‘);
// Older Methods ❌
// HTML collection - Requires iteration
const items = document.getElementsByClassName(‘item‘);
// Avoids direct DOM manipulation
const modalTriggers = Array.from(document.querySelectorAll(‘.trigger‘));
Key reasons to prefer querySelector
and getElementById
:
- Simplicity – No DOM collection processing required.
- Versatility – Use any valid CSS selector to target elements.
- Performance – Direct access without needing to iterate.
Legacy methods like getElementsByClassName
return live HTMLCollections, which require conversion to arrays before we can iterate or access directly. This extra processing can get expensive, especially in hot code paths that run frequently.
Adding Classes with classList
Once we have an element reference, we can use the classList
property to manipulate its classes:
const panel = document.querySelector(‘.panel‘);
// Add
panel.classList.add(‘active‘);
// Remove
panel.classList.remove(‘active‘);
// Toggle
panel.classList.toggle(‘active‘);
classList
has the following advantages compared to directly setting className
:
- Browser support is excellent – over 95% globally as of Feb 2023. Only issue is IE8 and below.
- Simple API avoids dealing with strings directly.
- Methods gracefully handle invalid class names without errors.
- Batch adding classes avoids multiple separate DOM changes.
We can pass multiple classes to .add()
and .remove()
to change several at once:
// Add multiple classes
panel.classList.add(‘expanded‘, ‘active‘);
// Remove multiple
panel.classList.remove(‘expanded‘, ‘active‘);
And even check if an element contains a specific class:
if(panel.classList.contains(‘active‘)) {
// Element has active class
}
There are also quite a few less commonly used classList
methods, like .replace()
and toggling based on a condition with .toggle()
. See documentation for all available methods.
Browser Support and Polyfills
As mentioned earlier, classList
support is excellent in all modern browsers. But there are some edge cases around legacy browser support worth mentioning:
- IE9 and below do not support classList at all.
- Some early Android Browser versions implemented an incomplete version missing some functionality.
For supporting these legacy browsers, we have a couple options:
-
Use a Polyfill – Automatically patch and shim missing functionality. classList.js is a popular choice.
-
Manually detect and fallback – Check if classList exists on element, and if not, use a custom className setter function with similar behavior.
Here is an example manual fallback helper function:
function setClass(el, className, isAdd) {
if (el.classList) {
el.classList[isAdd ? ‘add‘ : ‘remove‘](className);
} else {
const existing = el.className.split(‘ ‘);
const index = existing.indexOf(className);
if (isAdd && index < 0) {
existing.push(className);
}
if (!isAdd && index >= 0) {
existing.splice(index, 1);
}
el.className = existing.join(‘ ‘);
}
}
This simulates .add()
/.remove()
functionality by directly handling the className string.
So in summary – for modern browsers implementing classList natively use it directly. In edge cases of legacy browser support either use a polyfill or manually handle updates.
Setting class name Directly
We can also completely overwrite an element‘s classes by setting className
directly:
const button = document.querySelector(‘.btn‘);
function toggleActive() {
if(button.className === ‘btn‘) {
// Set classes
button.className = ‘btn active pulse‘;
} else {
// Remove all classes
button.className = ‘btn‘;
}
}
Performance Advantages
Changing an element‘s entire className
can actually be faster than toggling individual classes in some cases.
Benchmarks have shown assigning className
can outperform .add()
/.toggle()
by 2-4x depending on number of classes being changed.
For example, this simple className update:
el.className = ‘class1 class2‘;
…is significantly faster than:
el.classList.add(‘class1‘);
el.classList.add(‘class2‘);
Reason being – updating classList
requires changing multiple DOM properties behind the scenes. Wherease className
acts on the single class attribute.
However, keep in mind manually building up long className strings can get messy. classList
promotes cleaner code.
Pitfalls to Avoid
One thing to watch out for when directly setting className
is all existing classes get replaced. For example:
el.className = ‘new-class‘;
…would wipe out any classes that were already applied to the element!
To safely append new classes, ensure you concatenate onto the previous string:
// Append class rather than overwrite
el.className += ‘ extra-class‘;
Or manually compose strings while preserving existing classes:
const prevClasses = el.className;
el.className = `${prevClasses} new-class`.trim();
So in summary, although directly setting className
can provide performance advantages, it requires more care to handle properly compared to classList
methods.
Putting Into Practice
Now that we‘ve covered core concepts and APIs, let’s walk through some practical use cases for dynamic classes.
Toggling Interface States
A common example is toggling visibility of UI components, like opening and closing menus.
Here is sample markup for navigation menu with open/close buttons:
<button class="open-menu">≡</button>
<nav>
<div class="menu">
...
</div>
<button class="close-menu">×</button>
</nav>
We can style the menu to initially be hidden:
.menu {
display: none;
}
.menu.visible {
display: block;
}
Then toggle a visible
class from JavaScript to show/hide:
let menuOpen = false;
function toggleMenu() {
const menu = document.querySelector(‘.menu‘);
if(!menuOpen) {
menu.classList.add(‘visible‘);
} else {
menu.classList.remove(‘visible‘);
}
menuOpen = !menuOpen; // Inverse boolean
}
openMenuBtn.addEventListener(‘click‘, toggleMenu);
closeMenuBtn.addEventListener(‘click‘, toggleMenu);
This workflow applies for all kinds of UI components – tabs, modals, dropdowns etc. Toggling classes allows clean separation of concerns between CSS, JS and markup.
Creating Image Sliders
Another great use case is image sliders and carousels – showing one slide at a time using classes.
Consider this basic slider markup:
<div class="slider">
<img src="img1.jpg">
<img src="img2.jpg">
<img src="img3.jpg">
</div>
We use classes to decide which to show:
.slider img {
display: none;
}
.slider img.active {
display: block;
}
And can now toggle the active
class in JavaScript:
let currentIndex = 0;
function showNextSlide() {
// Remove active class from all
slides.forEach(slide => slide.classList.remove(‘active‘));
// Get current slide and loop index
currentIndex++;
if(currentIndex >= slides.length) {
currentIndex = 0;
}
const currentSlide = slides[currentIndex];
// Activate next slide
currentSlide.classList.add(‘active‘);
}
So applying this pattern of toggle one element dynamically at a time is extremely powerful for sliders, tabs,accordions and more.
Complex Component Logic
Taking this concept even further, we can build entire components with logic driven by class toggling.
For example form elements to handle validation states:
<form>
<input class="text-input">
<div class="messages">
<span class="error"></span>
<span class="success"></span>
</div>
<button class="submit-btn">Submit</button>
</form>
Some simplified logic:
const input = document.querySelector(‘.text-input‘);
const errorMsg = document.querySelector(‘.error‘);
const submitBtn = document.querySelector(‘.submit-btn‘);
// Validation check
function validateInput() {
if(input.value.length > 5) {
// Valid
input.classList.add(‘valid‘);
input.classList.remove(‘invalid‘);
errorMsg.classList.remove(‘active‘);
// Allow submit
submitBtn.disabled = false;
} else {
// Invalid
input.classList.add(‘invalid‘);
input.classList.remove(‘valid‘);
errorMsg.classList.add(‘active‘);
submitBtn.disabled = true;
}
}
input.addEventListener(‘input‘, validateInput);
Here classes both communicate current validation state and alter styling/behavior accordingly.
This is a very common pattern for handling form logic and component state changes.
Optimization Considerations
One downside to excessive class manipulation is it can incur DOM reflows, where the browser must recalculate layout. This is especially true if changing styling that affects document flow, like toggling between display: none;
and display: block;
We can reduce the quantity of reflows by avoiding active DOM manipulation and batching changes together.
Use DocumentFragment
For example instead of toggling classes on elements directly in the live DOM:
tabs.forEach(tab => {
tab.classList.remove(‘active‘);
});
targetTab.classList.add(‘active‘);
…we can build up changes in a DocumentFragment instead:
// Fragment avoids live DOM mutations
const docFrag = document.createDocumentFragment();
tabs.forEach(tab => {
// Clone nodes to fragment
const tabFrag = tab.cloneNode(true);
tabFrag.classList.remove(‘active‘);
docFrag.appendChild(tabFrag);
});
const activeTabFrag = targetTab.cloneNode(true);
activeTabFrag.classList.add(‘active‘);
docFrag.appendChild(activeTabFrag);
// Now, apply fragment in one shot
container.innerHTML = ‘‘;
container.appendChild(docFrag);
This batches changes together into a single redraw instead of multiple, producing better performance.
There are also other advanced techniques like using requestAnimationFrame()
for timing class application carefully in rendering cycles.
In summary – excessive DOM mutations can be expensive, so employ batching/fragment strategies in performance critical code.
Conclusion and Key Takeaways
The ability to dynamically manipulate CSS classes is an extremely valuable tool for web developers building interactive applications. Here is a recap of best practices covered in this guide:
-
Utilize
classList
for simple, cross-browser compliant class toggling. Methods like.add()
,.remove()
and.toggle()
to manipulate classes. -
Set
className
directly for better performance in certain edge cases. But handle with care to avoid clearing existing classes accidentally. -
Optimize class toggling performance through batching, using document fragments, and avoiding unnecessary layout calculations.
-
Common applications include toggling interface states, creating image sliders/carousels, applying form validation, and more.
Of course we‘ve only scratched the surface of techniques and real-world use cases for dynamic classes. For further learning check out resources like:
- Using the classList API (MDN)
- Boost Application Performance Using ClassList API
- Everything You Need To Know About the CSS Class Toggling API
But I hope this guide gives all the knowledge needed to start implementing dynamic class toggling effectively in your web projects! Let me know if you have any other questions.