
Using classList in JavaScript: add(), remove(), toggle(), and contains()

Before classList, manipulating CSS classes from JavaScript often meant wrestling with long class‑name strings by hand. That worked, technically, but it was brittle and awkward. Developers ended up splitting strings, concatenating them back together, and quietly introducing bugs such as duplicate classes or partial matches.
classList made this much cleaner.
If you are new to front‑end JavaScript, classList is one of the first DOM APIs worth getting comfortable with because a lot of simple UI behaviour depends on it. Menus open and close by toggling classes. Validation messages show and hide by adding classes. Components move between visual states by swapping classes.
Once you understand classList, those updates stop feeling like messy string surgery and start feeling like straightforward state changes.
What classList is
classList is a property on DOM elements that exposes the element's classes as a list‑like interface. Instead of working with one big class string directly, you get methods for adding, removing, checking, and toggling individual classes.
For example:
const panel = document.querySelector<HTMLElement>('.panel');console.log(panel?.classList);That gives you a DOMTokenList, which is just the browser's way of representing the set of classes on that element.
add() adds one or more classes
The simplest use is adding a class.
panel?.classList.add('panel--open');That is much cleaner than reading the class string, appending text manually, and writing it back.
You can also add more than one class at once:
panel?.classList.add('panel--open', 'panel--visible');If a class is already present, add() will not duplicate it. That is another reason it is safer than building class strings by hand.
remove() removes classes cleanly
Removing a class is just as straightforward:
panel?.classList.remove('panel--open');Again, this avoids the kind of brittle string replacement that used to leave extra spaces or remove the wrong substring accidentally.
You can remove several classes together too:
panel?.classList.remove('panel--open', 'panel--visible');toggle() is perfect for simple UI states
toggle() is one of the most useful methods because so many interactions are really just switches between two class states.
const button = document.querySelector<HTMLButtonElement>('.menu-toggle');const menu = document.querySelector<HTMLElement>('.menu');button?.addEventListener('click', () => { menu?.classList.toggle('menu--open');});If the class is missing, toggle() adds it. If it is already there, toggle() removes it.
That makes it ideal for:
- dropdowns
- accordions
- menus
- selected states
- active tabs
There is also a slightly less‑known second form where you provide a boolean to force the state explicitly:
menu?.classList.toggle('menu--open', true);menu?.classList.toggle('menu--open', false);That is useful when the UI state comes from logic rather than from a simple "flip whatever it currently is" interaction.
contains() lets you ask about the current state
Sometimes you want to know whether a class is present before deciding what to do.
if (menu?.classList.contains('menu--open')) { console.log('Menu is open');}That can be handy, although in many cases it is even better to drive state from a more explicit variable rather than interrogating the DOM repeatedly.
Still, contains() is useful when you genuinely need to read the current visual state back from the element.
Why className is less pleasant
You can manipulate classes through element.className, because that gives you the raw class string.
panel.className = 'panel panel--open';But this is more fragile for a few reasons:
- you can accidentally overwrite existing classes
- you have to manage spaces manually
- checking for a class becomes string logic instead of class logic
- partial matches can cause mistakes
Imagine checking for "open" inside a class string. That can become messy quickly if another class includes the same substring.
classList avoids that whole category of problem because it works with individual class tokens instead of one shared text field.
A Realistic Example: Validation State
Imagine a form field that should show an error state when invalid.
const input = document.querySelector<HTMLInputElement>('#email');const field = document.querySelector<HTMLElement>('.field');input?.addEventListener('blur', () => { const hasValue = (input.value ?? '').trim() !== ''; field?.classList.toggle('field--error', !hasValue);});That reads quite cleanly. The class is not being treated like a weird string blob. It is being treated as a state flag.
That is really the best way to think about classList in front‑end code. It lets you make DOM state transitions more explicit.
classList works best when CSS states are meaningful
The API is clean, but it still helps to use sensible class names.
If your CSS uses classes such as:
menu‑‑openfield‑‑errortab‑‑activemodal‑‑visible
then the JavaScript reads naturally.
If the classes are vague or overstuffed, the JavaScript becomes harder to understand no matter how good the API is.
That is not really a classList issue. It is a reminder that front‑end code becomes clearer when CSS states are named deliberately.
toggle() can be overused
It is worth saying this because it catches people later.
Simple toggles are great for simple interactions. They are less ideal when the UI has several possible states or when state should be driven from data rather than whatever the DOM happened to look like a moment ago.
For example, if a component can be:
- loading
- open
- disabled
- error
then just blindly toggling classes can get messy. In those cases it is often better to calculate the intended state and apply classes explicitly.
So classList is a very useful API, but like most DOM tools, it works best when the surrounding state model is sensible.
A Small but Useful Detail
classList is available on elements, not on arbitrary values.
That sounds obvious, but it explains a lot of beginner errors. If a selector returns null, then element.classList.add(...) will fail because there is no element there at all.
That is why it is common to see safe checks such as:
const panel = document.querySelector<HTMLElement>('.panel');if (!panel) { return;}panel.classList.add('panel--ready');Wrapping up
classList gives JavaScript a much cleaner way to work with CSS classes than direct string manipulation through className. Methods such as add(), remove(), toggle(), and contains() make common UI changes easier to read, safer to write, and less error‑prone in ordinary front‑end work.
Key Takeaways
classListlets you manage classes as individual tokens rather than one big string.add()andremove()are cleaner and safer than manual class‑string editing.toggle()is ideal for simple open, closed, active, and visible states.contains()lets you inspect whether a class is currently present.- The API works best when your CSS state classes are named clearly and used deliberately.
For everyday front‑end behaviour, classList is one of those DOM APIs that pays off almost immediately once it becomes part of your normal toolkit.
Related Articles

!important in CSS. 
Angular Standalone Components: Do We Still Need Modules? Angular Standalone Components: Do We Still Need Modules?

Array.find(), Array.some(), and Array.every() in JavaScript. Array.find(),Array.some(), andArray.every()in JavaScript
DOMContentLoaded vs. load in JavaScript. DOMContentLoadedvs.loadin JavaScript
Automatically Generate urllist.txt from sitemap.xml. Automatically Generate
urllist.txtfromsitemap.xml
Detecting and Dealing with Website Theft. Detecting and Dealing with Website Theft

The JavaScript map() Method. The JavaScript
map()Method
Promise.all() vs. Promise.race() in JavaScript. Promise.all()vs.Promise.race()in JavaScript
The React Context API: When to Use It and When Not to. The React Context API: When to Use It and When Not to

Using RxJS for State Management in Angular. Using RxJS for State Management in Angular
What is an HTML Entity? What is an HTML Entity?

CSS visibility: Hiding Elements Without Affecting Layout. CSS
visibility: Hiding Elements Without Affecting Layout