JavaScript classList: add, remove, toggle and contains Explained
Learn to control CSS classes from JavaScript with classList.add, remove, toggle, contains and replace, and build dark mode, tabs and menus with real examples.
What you will learn
In the last lesson you changed styles with element.style. That works, but for most jobs there is a cleaner way: write the look in your CSS file as a class, and let JavaScript simply switch the class on and off. The tool for this is classList. In this lesson you will learn classList.add, remove, toggle, contains and replace, and build real features such as dark mode, tabs, a mobile menu and form errors.
Why classes are better than inline styles
Imagine a form field that must look red when it has an error. You could write this:
input.style.border = "2px solid red";
input.style.background = "#fee2e2";
input.style.color = "#991b1b";
That is three lines of CSS hidden inside JavaScript. If a designer wants to change the red later, they must dig into your script. Now compare:
.input-error {
border: 2px solid red;
background: #fee2e2;
color: #991b1b;
}
input.classList.add("input-error");
The look lives in the CSS file, JavaScript only decides when. This is called separation of concerns, and it is how professional sites are built.
element.style |
element.classList |
|
|---|---|---|
| Where does the CSS live? | Inside JavaScript | In your .css file |
Works with media queries, :hover, transitions? |
No (only plain values) | Yes |
| Easy to undo? | Need to remember each property | Remove one class |
| Best for | Values from data (a 65% width) | States (active, hidden, error, dark) |
The sample page
All examples use this page, which is also loaded in Practice in Editor.
<h1 id="title" class="title">classList Practice</h1>
<div id="box" class="box">I am a box</div>
<button id="btn" class="btn">Click target</button>
<div id="alert" class="alert hidden">Saved successfully!</div>
.box {
padding: 16px;
margin: 12px 0;
border: 2px solid #94a3b8;
border-radius: 8px;
transition: all 0.3s;
}
.highlight {
background: #fef9c3;
border-color: #eab308;
}
.big {
font-size: 24px;
}
.rounded {
border-radius: 24px;
}
.btn.active {
background: #2563eb;
color: white;
}
.hidden {
display: none;
}
Start with:
const box = document.getElementById("box");
What is classList?
An element’s class attribute can hold many classes separated by spaces: class="box highlight big". The classList property gives you that list as a small helper object (a DOMTokenList), with simple methods to change it. You never have to deal with the spaces yourself.
console.log(box.className); // "box" (a plain string)
console.log(box.classList); // DOMTokenList ["box", value: "box"]
console.log(box.classList.length); // 1
console.log(box.classList[0]); // "box"
classList.add()
Adds one or more classes. If a class is already there, nothing changes (no duplicates).
box.classList.add("highlight");
box.classList.add("big", "rounded"); // several at once
box.classList.add("big"); // already there, ignored
console.log(box.className); // "box highlight big rounded"
Run it on the practice page: the box turns yellow, grows and gets round corners, with a smooth transition because of the transition in the CSS.
You can also add a list of classes from an array with the spread syntax:
const classes = ["highlight", "big"];
box.classList.add(...classes);
classList.remove()
Removes one or more classes. If a class is not there, nothing happens and there is no error.
box.classList.remove("big");
box.classList.remove("rounded", "highlight");
box.classList.remove("not-there"); // safe
console.log(box.className); // "box"
classList.toggle()
toggle is a light switch. If the class is there, it removes it. If it is not there, it adds it. It also returns true if the class is now present, and false if it was removed.
console.log(box.classList.toggle("highlight")); // true (it was added)
console.log(box.classList.toggle("highlight")); // false (it was removed)
The optional second argument forces the result. true means always add, false means always remove:
box.classList.toggle("highlight", true); // adds it, even if called many times
box.classList.toggle("highlight", false); // removes it
The forced version is perfect when the state comes from a condition:
const isOnline = true;
box.classList.toggle("highlight", isOnline); // add when online, remove when offline
classList.contains()
Asks “does this element have this class?” and answers true or false.
console.log(box.classList.contains("box")); // true
console.log(box.classList.contains("highlight")); // false
if (box.classList.contains("highlight")) {
console.log("The box is highlighted");
}
classList.replace()
Swaps one class for another. It returns true if the old class existed and was replaced, and false if not.
box.classList.add("highlight");
console.log(box.classList.replace("highlight", "big")); // true
console.log(box.className); // "box big"
console.log(box.classList.replace("missing", "x")); // false (nothing changed)
Looping over the classes
classList is not an array, but it has forEach, length and item(), and you can use for...of:
box.classList.add("highlight", "big");
box.classList.forEach(function (name) {
console.log(name); // box, highlight, big
});
for (const name of box.classList) {
console.log(name);
}
console.log([...box.classList]); // a real array: ["box", "highlight", "big"]
All methods at a glance
| Method / property | What it does | Returns |
|---|---|---|
add("a", "b") |
Adds one or more classes (no duplicates) | Nothing |
remove("a", "b") |
Removes one or more classes | Nothing |
toggle("a") |
Adds if missing, removes if present | true if now present |
toggle("a", force) |
true = add, false = remove |
true if now present |
contains("a") |
Checks if the class exists | true / false |
replace("old", "new") |
Swaps one class for another | true if replaced |
length |
Number of classes | A number |
item(0) or [0] |
The class at a position | A string, or null |
forEach, for...of, [...] |
Loop over the classes |
classList vs className
className is the whole class attribute as one string. Setting it replaces everything:
box.className = "highlight"; // "box" is gone! Only "highlight" is left
That is dangerous, because you can accidentally remove classes that other code or your CSS depends on. classList only touches the classes you name.
className |
classList |
|
|---|---|---|
| Type | One string | List with methods |
| Add a class | el.className += " big" (easy to forget the space) |
el.classList.add("big") |
| Remove one class | Hard (string work) | el.classList.remove("big") |
| Replace everything | el.className = "a b" |
Not needed |
Use className only when you really want to overwrite all classes. For everything else, use classList.
Real use cases
1. Dark mode toggle
The CSS describes the dark theme under a class. JavaScript only switches that class on <body>.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dark Mode</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
background: #ffffff;
color: #111827;
transition: background 0.3s, color 0.3s;
}
body.dark {
background: #111827;
color: #f9fafb;
}
button { padding: 8px 14px; font-size: 16px; cursor: pointer; }
</style>
</head>
<body>
<h1>My Blog</h1>
<p>Switch the theme with the button.</p>
<button id="theme">Switch to dark</button>
<script>
const themeButton = document.getElementById("theme");
themeButton.addEventListener("click", function () {
const isDark = document.body.classList.toggle("dark");
themeButton.textContent = isDark ? "Switch to light" : "Switch to dark";
});
</script>
</body>
</html>
Look at how toggle returns true or false. That return value tells us which button text to show. (You will learn to remember the choice after a refresh in the Browser Storage lesson.)
2. Tabs: only one is active
The idea: first remove active from every tab, then add it to the clicked one.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tabs</title>
<style>
body { font-family: sans-serif; padding: 20px; max-width: 480px; }
.tabs { display: flex; gap: 6px; border-bottom: 2px solid #e5e7eb; }
.tab {
padding: 8px 14px;
border: none;
background: none;
cursor: pointer;
font-size: 16px;
border-bottom: 3px solid transparent;
}
.tab.active { border-bottom-color: #2563eb; color: #2563eb; font-weight: bold; }
.panel { display: none; padding: 16px 0; }
.panel.active { display: block; }
</style>
</head>
<body>
<div class="tabs">
<button class="tab active" data-target="html">HTML</button>
<button class="tab" data-target="css">CSS</button>
<button class="tab" data-target="js">JavaScript</button>
</div>
<div class="panel active" id="html">HTML gives a page its structure.</div>
<div class="panel" id="css">CSS gives a page its style.</div>
<div class="panel" id="js">JavaScript gives a page its behaviour.</div>
<script>
const tabs = document.querySelectorAll(".tab");
const panels = document.querySelectorAll(".panel");
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
// 1. Switch everything off
tabs.forEach(function (t) { t.classList.remove("active"); });
panels.forEach(function (p) { p.classList.remove("active"); });
// 2. Switch the chosen ones on
tab.classList.add("active");
document.getElementById(tab.dataset.target).classList.add("active");
});
});
</script>
</body>
</html>
The tab uses a data-target attribute (from the attributes lesson) to know which panel it controls. To add a fourth tab, you only add HTML. The script does not change.
3. Mobile menu and FAQ accordion
Both use the same trick: one class, open, decides what shows. CSS does the work, and JavaScript only toggles.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Accordion</title>
<style>
body { font-family: sans-serif; padding: 20px; max-width: 480px; }
.item { border: 1px solid #d1d5db; border-radius: 8px; margin-bottom: 8px; overflow: hidden; }
.question { width: 100%; padding: 12px; text-align: left; font-size: 16px; background: #f3f4f6; border: none; cursor: pointer; }
.answer { max-height: 0; padding: 0 12px; overflow: hidden; transition: max-height 0.3s, padding 0.3s; }
.item.open .answer { max-height: 120px; padding: 12px; }
.item.open .question { background: #dbeafe; font-weight: bold; }
</style>
</head>
<body>
<div class="item">
<button class="question">What is classList?</button>
<div class="answer">A helper to add, remove and toggle CSS classes.</div>
</div>
<div class="item">
<button class="question">Why not use style?</button>
<div class="answer">Classes keep your CSS in the stylesheet and work with transitions.</div>
</div>
<script>
document.querySelectorAll(".question").forEach(function (button) {
button.addEventListener("click", function () {
button.closest(".item").classList.toggle("open");
});
});
</script>
</body>
</html>
Notice button.closest(".item"), from the traversal lesson. It goes up from the clicked button to the whole item. The .open class on the item decides how the question and answer look, and transition makes it smooth. For a mobile navigation menu, you do exactly the same, toggling an open class on the <nav>.
4. Form validation: error and success
toggle with the second argument is ideal when the state comes from a check.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Validation</title>
<style>
body { font-family: sans-serif; padding: 20px; max-width: 360px; }
input { width: 100%; padding: 8px; font-size: 16px; box-sizing: border-box; border: 2px solid #d1d5db; border-radius: 6px; }
input.valid { border-color: #16a34a; }
input.invalid { border-color: #dc2626; background: #fef2f2; }
.hint { color: #dc2626; font-size: 14px; }
.hidden { display: none; }
</style>
</head>
<body>
<label for="username">Username (at least 4 characters)</label>
<input id="username" type="text">
<p class="hint hidden" id="hint">Username is too short.</p>
<script>
const username = document.getElementById("username");
const hint = document.getElementById("hint");
username.addEventListener("input", function () {
const ok = username.value.length >= 4;
username.classList.toggle("valid", ok);
username.classList.toggle("invalid", !ok);
hint.classList.toggle("hidden", ok);
});
</script>
</body>
</html>
Each toggle(name, condition) line says “this class should be on exactly when the condition is true”. You do not need any if/else.
5. Show and hide with a hidden class
This is the most common use of all: show a message, a modal or a loading spinner by removing a class, and hide it by adding the class back.
const alertBox = document.getElementById("alert");
alertBox.classList.remove("hidden"); // show
setTimeout(function () {
alertBox.classList.add("hidden"); // hide again after 3 seconds
}, 3000);
6. Highlight the current page in a menu
Remove the highlight from all the links and add it to the one that matches the current page:
const here = location.pathname;
document.querySelectorAll("nav a").forEach(function (a) {
a.classList.toggle("current", a.getAttribute("href") === here);
});
Careful with spaces and empty names
The names you pass to classList must be single words without spaces. Otherwise you get an error:
box.classList.add("big rounded"); // DOMException: the token contains a space
box.classList.add("big", "rounded"); // correct: two arguments
box.classList.add(""); // SyntaxError: empty token
Common mistakes
- Adding a dot.
classList.add(".active")adds a class named.activewith the dot. Writeadd("active"). Only CSS selectors use dots. - Putting spaces in one name.
add("big rounded")throws an error. Pass each class as its own argument. - Using
className =and wiping out other classes. UseclassList.addunless you want to overwrite all. - Expecting
classListto be an array. It has nomaporfilter. Use[...el.classList]if you need them. - Forgetting to write the CSS.
classList.add("highlight")does nothing visible if.highlightdoes not exist in your CSS. Check the spelling in both places. - Not removing the old state. For tabs, you must remove
activefrom all the others first, or many tabs stay active. - Using
togglewhen you need a fixed state. If the code may run twice,toggleflips back. Useadd,removeortoggle(name, force)when you know what you want. - Specificity problems. If your class does not seem to work, another selector may be stronger. Check it in DevTools, like in the CSS specificity lesson.
- Using inline
stylefor things that should be classes. If you writestyle.display = "none"in many places, it is time to use ahiddenclass.
Practice
- Print the box’s
className,classList.lengthand whether itcontains("box"). - Add the classes
highlightandbigto the box with oneaddcall. Then removebig. - Call
add("big")three times in a row and printclassName. How manybigwords are there? - Use
toggleon the box and print what it returns each time. Then usetoggle("rounded", true)twice. - Replace
highlightwithbigusingreplace, and print whether it worked. - Print every class of the box with
forEach, and then convert the list to an array with the spread syntax. - Make the button switch an
activeclass on and off when it is clicked, and change its text to match. - Show the
#alertby removinghidden, and hide it again after 2 seconds withsetTimeout. - Build the dark mode page. Add a second colour theme (a
.sepiaclass) with a second button. Make sure only one theme class is on at a time. - Challenge: in the tabs example, add a keyboard feature so pressing the
ArrowRightkey moves to the next tab. (Hint: usenextElementSiblingfrom the traversal lesson.)
Recap
classListlets you change an element’s CSS classes without touching the others or worrying about spaces.add("a", "b")adds classes,remove("a", "b")removes them, and both are safe to repeat.toggle("a")flips a class and returnstrueorfalse.toggle("a", condition)forces it on or off, which replaces manyif/elseblocks.contains("a")checks for a class, andreplace("old", "new")swaps one for another.classNameis a single string, and setting it overwrites everything. PreferclassList.- Put the look in CSS and use JavaScript only to switch classes. Use
styleonly for values that come from data. - Class names are written without dots, and each name is one word with no spaces.
- Classes power real features: dark mode, tabs, accordions, mobile menus, form errors, and show/hide messages.
- Next you will learn how to create and remove elements with
createElement,append,prependandremove.