Styles & classes
Change how elements look from JavaScript with className, classList, inline styles, getComputedStyle, and CSS custom properties.
- 01Choose the right toolUse classes for designed states, inline styles for truly dynamic values, and custom properties to feed values into CSS.
- 02Edit classes safelyCompare
classNamewith the liveclassListtoken API, includingtoggle(token, force)andreplace. - 03Read the final resultUse
getComputedStylefor resolved values while avoiding style recalculation in tight loops.
JavaScript chooses the look; CSS designs it
A DOM element can look different because of stylesheet rules, classes, inline styles, inherited values, browser defaults, and custom properties. JavaScript can touch all of those, but the professional skill is choosing the smallest tool that says what you mean.
The rule of thumb is simple: classes for designed states, inline styles for live numbers, and custom properties when CSS should still do the drawing. Then use getComputedStyle when you need to inspect the final result the browser actually resolved.
CSS describes the outfits. JavaScript chooses which outfit an element wears, paints a few truly dynamic details when needed, and can take a computed-style photo of the final look.
Think of a button as a person and your stylesheet as a wardrobe. JavaScript should usually not sew a new outfit in the moment. It should say “wear the selected outfit now” by changing a class.
- In real life: A wardrobe full of named outfits
- In JavaScript: CSS classes such as
is-open,warning, andtheme-dark - In real life: Choosing the raincoat today
- In JavaScript: JavaScript adds a class with
classList.add() - In real life: Taking off the hoodie
- In JavaScript: JavaScript removes a class with
classList.remove() - In real life: The tailor who designed the clothes
- In JavaScript: Your CSS stylesheet
Where the analogy stops: A person can mix clothes however they like. CSS still follows the cascade: specificity, order, inheritance, and inline styles can change which rule wins.
In this lesson you will operate a real classList, explore the traps of el.style, read resolved values with getComputedStyle, and use a custom property as a tiny theme thermostat.
className and classList
INTERACTIVEclassName is the class attribute as one string. It has this name because class was already a reserved word in JavaScript. Setting it is sometimes useful when you want to replace the whole class attribute at once:
button.className = "button button--primary is-loading";
console.log(button.className);Most day-to-day UI changes are better with classList. It is a live DOMTokenList: a token API connected to the element’s real attribute. It prevents duplicate class tokens and gives you purpose-built methods.
Step through a tiny model of classList. The real playground below uses actual DOM methods; this replay makes the token changes visible.
script
const classes = new Set(card.className.split(" "));classes.add("is-raised");card.classList.toggle("is-selected");classes.delete("card");const replaced = classes.delete("is-raised");if (replaced) classes.add("is-flat");card.className = [...classes].join(" ");console.log(card.className);Now try the same operations on a real element. Notice the return values: toggle(token, force) returns the resulting state, and replace(oldToken, newToken) returns a boolean telling you whether the old token existed.
const card = document.querySelector(".demo-card");card.classList.add("is-raised");card.classList.remove("is-muted");card.classList.toggle("is-selected");card.classList.toggle("is-selected", true);card.classList.replace("is-raised", "is-flat");card.classList.contains("is-selected");console.log(card.className);demo-card is-muteddemo-card | is-mutedReady. Pick an operation.
classList.add("one two") is invalid because a token cannot contain spaces. Add two tokens as add("one", "two") or call add twice.
The style property
LABel.style reflects only the inline style attribute. If a class makes text purple, el.style.color is still an empty string until JavaScript or HTML sets an inline color.
Inline style is like painting one detail directly onto the person. It is perfect for a one-off dynamic number, such as a progress bar width or pointer coordinate, but clumsy for a complete state like “error”, “selected”, or “dark theme”.
- In real life: Paint brushed directly on a jacket
- In JavaScript: An inline declaration such as
style.backgroundColor = 'gold' - In real life: Harder to reuse than a wardrobe outfit
- In JavaScript: Harder to share than a CSS class
- In real life: Very specific paint wins over normal clothes
- In JavaScript: Inline style beats normal stylesheet declarations
Where the analogy stops: Important stylesheet rules and the cascade still exist. Also, inline style only stores declarations you put on the element; it is not the whole final look.
Dot form uses camelCase property names such as backgroundColor. The generic methods use CSS spelling such as background-color. Invalid values are silently ignored, and many lengths need a unit.
const box = document.querySelector(".box");box.classList.add("warning");console.log(box.style.color);box.style.backgroundColor = "gold";box.style.setProperty("border-radius", "16px");box.style.width = 100;console.log(box.style.width);box.style.width = "100px";box.style.removeProperty("background-color");The box starts with its color from a class, not inline style.
| Tool | Best for | Avoid when |
|---|---|---|
classList | Named states designed in CSS: open, active, error, dark | The value changes continuously, like mouse position |
style.property | Known CSS properties with JavaScript camelCase names | You need a custom property or a kebab-case name |
style.setProperty() | Kebab-case properties, custom properties, and optional important priority | A class would express the state more clearly |
style.cssText | Replacing the whole inline style string intentionally | You only meant to update one property |
getComputedStyle
BROWSER PHOTOIf el.style is the inline paint can, getComputedStyle is a photo of the final look after the cascade, inheritance, default styles, and layout-dependent values have been considered. The values are resolved: colors often appear as rgb(...), and many lengths appear in pixels.
When you need to know what the browser ended up using, take a computed-style photo. It is especially handy when a value came from a class or inherited rule rather than inline style.
- In real life: A photo after everyone got dressed
- In JavaScript:
getComputedStyle(element) - In real life: The photo shows the visible result
- In JavaScript: Resolved values after rules, inheritance, and defaults
- In real life: You cannot edit the clothes by drawing on the photo
- In JavaScript: The returned style declaration is read-only for changing the page
Where the analogy stops: The photo comparison sounds frozen, but browsers may expose a live object. Treat it as something to read, not mutate, and do not call it repeatedly in tight loops.
const computed = getComputedStyle(box);console.log(computed.color);console.log(computed.width);console.log(box.style.color);console.log(getComputedStyle(box, "::before").content);No computed photo yet.
Read computed values after the demo element exists in the browser.
getComputedStyle may force the browser to recalculate styles. Read once, store what you need, and avoid tight loops.The second argument reads pseudo-elements, so getComputedStyle(el, "::before") can inspect generated content. Because a computed-style read can force style recalculation, read once outside hot animation loops and reuse the values you need.
CSS custom properties
THEME LABCSS custom properties are names like --hue or --brand-color. They are case-sensitive, inherit like normal CSS properties, and must be changed with setProperty and read with getPropertyValue. Dot notation cannot spell a property that starts with --.
Instead of JavaScript painting every border, background, and badge, set one custom property and let CSS do the design work. This keeps dynamic data in JavaScript and visual relationships in CSS.
- In real life: One thermostat setting
- In JavaScript: One value such as
--hue: 210 - In real life: Every room reads that setting
- In JavaScript: Many CSS rules use the same custom property
- In real life: Turn the dial once
- In JavaScript: JavaScript calls
style.setProperty('--hue', value)
Where the analogy stops: A thermostat controls temperature, while a custom property is just text until a CSS property uses it. Invalid final CSS can still be ignored.
:root { --hue: 210; }.card { background: hsl(var(--hue) 80% 45%); } panel.style.setProperty("--hue", "320");const hue = getComputedStyle(panel).getPropertyValue("--hue").trim();210The frame reads back --hue as waiting. The parent changed one value; several CSS rules reacted.
You can also pass a third argument to setProperty, such as setProperty("color", "red", "important"), but use it sparingly. Newer browsers support typed, animatable custom properties with @property in CSS and CSS.registerProperty in JavaScript; feature-detect before depending on them.
Where you will use this
SORT ITReal interfaces mix all three tools. A menu opens with a class, a dragged panel follows the pointer with a transform, and a theme editor passes one chosen hue into CSS as a custom property.
- Toggle dark mode for a whole page
- Move a tooltip to follow the mouse
- Let a user pick a brand hue
- Hide or show a completed item
- Set a progress bar to exactly 63%
- Make every card read
--hue
Choose the tool that communicates the job most clearly. Some real apps could use more than one; pick the best default.
function updateMenu(menu, open) {
menu.classList.toggle("is-open", open);
}
function moveTooltip(tooltip, x, y) {
tooltip.style.transform = `translate(${x}px, ${y}px)`;
}
function setBrandHue(panel, hue) {
panel.style.setProperty("--brand-hue", String(hue));
}Common misconceptions
- “
classNameandclassListare separate storage.” They are two views of the same class attribute. Change one and the other reflects it. - “
el.style.colortells me the visible color.” It only tells you the inline color. UsegetComputedStyle(el).colorfor the resolved color. - “JavaScript adds units for CSS lengths.” It does not.
width = 100is ignored by a real DOM style declaration; use"100px"or another valid unit. - “Inline style is always bad.” It is bad for reusable states, but excellent for real-time values such as coordinates, measured sizes, and progress numbers.
- “Computed styles are free to read anywhere.” They can force style recalculation, so avoid reading them in tight loops.
- “Custom properties are JavaScript variables.” They are CSS properties. JavaScript can set them, but CSS decides how they affect the final property values.
| API | Reads | Writes | Return values to remember |
|---|---|---|---|
className | The whole class attribute string | The whole class attribute string | A string |
classList | Live class tokens | Individual tokens | toggle returns state; replace returns success |
el.style | Inline declarations only | Inline declarations only | Invalid values are ignored |
getComputedStyle | Resolved final values | Nothing useful | Read-only resolved strings |
| Custom properties | Through computed style or inline style | Use setProperty | Names are case-sensitive |
Practice exercises
5 EXERCISESType exactly what the final console.log prints.
const el = { className: "card active" };
const list = new Set(el.className.split(" "));
list.delete("active");
list.add("hidden");
el.className = [...list].join(" ");
console.log(el.className);const el = { className: "card active" };
const list = new Set(el.className.split(" "));
list.delete("active");
list.add("hidden");
el.className = [...list].join(" ");
console.log(el.className);The tokens start as card and active. The code deletes active, adds hidden, joins the remaining tokens, and prints card hidden.
Why can the visible text have a color while this code still prints true?
const el = { style: { color: "" } };
console.log(el.style.color === "");const el = { style: { color: "" } };
console.log(el.style.color === "");The inline color value is an empty string, so comparing it with an empty string prints true. A class could still make the element visibly colorful.
Write the body for a helper that makes a tab active or inactive without branching.
function setActive(tab, active) {
// Add or remove the "is-active" class based on active.
}function setActive(tab, active) {
tab.classList.toggle("is-active", active);
}The force argument turns toggle into an explicit set operation. It returns the resulting state, but the helper does not need to use that value.
Predict the boolean.
const classes = new Set(["card", "raised"]);
const replaced = classes.delete("missing");
if (replaced) classes.add("flat");
console.log(replaced);const classes = new Set(["card", "raised"]);
const replaced = classes.delete("missing");
if (replaced) classes.add("flat");
console.log(replaced);The set does not contain missing, so the model prints false. Real classList.replace('missing', 'flat') also returns false.
Write the line that passes the number into CSS.
const card = document.querySelector(".card");
const hue = 280;
// Set --brand-hue on card so CSS can use it.const card = document.querySelector(".card");
const hue = 280;
card.style.setProperty("--brand-hue", String(hue));Custom properties must be set by name with setProperty. CSS can then reuse --brand-hue in several declarations.
Check your understanding
7 QUESTIONSQuestion 1 of 7When should you prefer
classList.add('is-open')over several inline style assignments?Choose an answer to see the explanation.
Question 2 of 7What does
el.style.colorread?Choose an answer to see the explanation.
Question 3 of 7What does this class-token model print?
Read the code, then predictconst classes = new Set(["card"]); const result = classes.has("active") ? false : (classes.add("active"), true); console.log(result); console.log([...classes].join(" "));Choose an answer to see the explanation.
Question 4 of 7Which method is required for CSS custom properties such as
--brand-hue?Choose an answer to see the explanation.
Question 5 of 7What does this plain object width example print?
Read the code, then predictconst style = { width: "" }; style.width = String(100); console.log(style.width);Choose an answer to see the explanation.
Question 6 of 7What is the best description of
getComputedStyle(el)?Choose an answer to see the explanation.
Question 7 of 7What does
classList.replace('old', 'new')return whenoldis missing?Choose an answer to see the explanation.
Key takeaways
classNameis the whole class attribute string;classListis the live token API.toggle(token, force)returns the resulting state, andreplacereturns whether it succeeded.el.stylereads and writes inline declarations only; invalid CSS values are silently ignored.getComputedStylereads resolved final values and can trigger style recalculation.- Custom properties are the bridge for dynamic values that CSS should reuse.
Final definition: styles and classes are the DOM tools for choosing designed states, applying dynamic inline values, and reading the browser’s final visual result.
Up next: Sizes, scrolling & coordinates.