Styles & classes
Change how elements look from JavaScript with className, classList, inline styles, getComputedStyle, and CSS custom properties.
- 01Choose the right toolUse a class for a look you designed in CSS, an inline style for a number that keeps changing, and a custom property to hand one value to CSS.
- 02Change classes safelyTell
className(one string) fromclassList(a live list of names). Usetoggle(token, force)andreplace, and know what each gives back. - 03Read the final lookUse
getComputedStyleto read the values the browser really uses, and don't call it again and again in a fast loop.
JavaScript chooses the look; CSS designs it
An element is one tag on the page, such as a button. Its look can come from several places:
- Stylesheet rules: CSS in a file or a
<style>tag, such as.warning { color: crimson; }. - Classes: names in the element's
classattribute. Each name picks rules from the stylesheet. - Inline styles: CSS written in the element's own
styleattribute. - Inherited values: values a child element takes from its parent, such as text color.
- Browser defaults: the look the browser gives each tag before any of your CSS.
- Custom properties: CSS values with a name you choose, such as
--hue.
JavaScript can change all of these. The skill is to pick the smallest tool that does the job. Here is the simple rule:
- Classes for looks you designed ahead of time, such as "open" or "error".
- Inline styles for live numbers, such as where the mouse is.
- Custom properties to hand one value to CSS, so CSS still does the drawing.
When you want to know the final look the browser worked out, use getComputedStyle.
CSS designs how each state looks. JavaScript picks which state an element is in, usually by adding or removing a class. For a live number, JavaScript can set one inline style. To read the final look, it calls getComputedStyle.
Think of a button as a person. Your stylesheet is the wardrobe, full of outfits a tailor made ahead of time. JavaScript does not sew a new outfit on the spot. It just says "wear the selected outfit now" by adding 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: Putting on 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 made the clothes
- In JavaScript: Your CSS stylesheet
In this lesson you will:
- add and remove classes on a real element with
classList, - see what
el.stylecan and cannot read, - read the final look with
getComputedStyle, - change a whole theme with one custom property.
className and classList
INTERACTIVEclassName is the element's class attribute as one string. It is not called class because class is a reserved word in JavaScript: a word the language keeps for itself. Setting className replaces every class at once:
button.className = "button button--primary is-loading";
console.log(button.className);Line 1 gives the button three classes and throws away any it had before. Line 2 prints the new string.
Most of the time you want to change just one class. The next example does that. Each name inside the class string is called a token.
const button = document.querySelector("button");
button.className = "button is-ready";
button.classList.add("is-busy");
button.classList.remove("is-ready");
console.log(button.className);- Line 1 finds the button.
- Line 2 replaces the whole string with
button is-ready. - Line 3 adds one token,
is-busy. - Line 4 removes one token,
is-ready. - Line 5 prints
button is-busy.
button stays.The button class stays, because add and remove touch only the token you name. That is why classList is safer than building the whole class string again on every click.
classList is a DOMTokenList: a list of the tokens, tied to the real class attribute. It is live, which means a change to the list changes the attribute right away, and the other way round. It never stores the same token twice. Its methods:
add(token)adds a token.remove(token)removes a token. If it is not there, nothing happens.toggle(token)adds the token if it is missing, and removes it if it is there.toggle(token, force)takes a second argument,force. Withtrueit only adds; withfalseit only removes.replace(oldToken, newToken)swaps one token for another.contains(token)answerstrueorfalse: is the token there?
Step through a small copy of classList, built from a Set. The classList console below uses a real element.
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 methods on a real element. Watch what two of them give back:
toggle(token, force)gives backtrueif the token is there after the call, andfalseif it is not.replace(oldToken, newToken)gives backtrueif the old token was there and got swapped, andfalseif it was missing.
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. Press a button to run one method.
classList.add("one two") throws an error, because a token cannot contain a space. To add two tokens, write add("one", "two"), or call add twice.
Tabs need one more step. A screen reader is a program that reads the page aloud for people who cannot see it. It does not see your CSS classes. It reads the aria-selected attribute to know which tab is chosen.
function showTab(tab, active) {
tab.classList.toggle("is-active", active);
tab.setAttribute("aria-selected", String(active));
}
const tab = document.querySelector("[role=tab]");
showTab(tab, true);
console.log(tab.className, tab.getAttribute("aria-selected"));- Line 2 adds
is-activewhenactiveistrue, and removes it when it isfalse. The class changes how the tab looks. - Line 3 writes the same
trueorfalseintoaria-selectedas a string. This tells the screen reader. - Line 8 prints
tab is-active true.
The class and the attribute do two different jobs, so update both whenever the tab changes.
The style property
LABel.style reads and writes only the element's inline style attribute. Say a class makes the text purple. Then el.style.color is still an empty string, "". It stays empty until JavaScript or the HTML sets a color in the style attribute itself.
An inline style is like paint brushed straight onto one jacket. It is great for one detail that keeps changing, such as the width of a progress bar or the spot where the mouse is. It is a poor choice for a whole look, such as "error", "selected", or "dark theme". Those belong in the wardrobe.
- In real life: Paint brushed straight onto one jacket
- In JavaScript: An inline style such as
style.backgroundColor = 'gold' - In real life: You can't hand that paint to another jacket
- In JavaScript: You can't reuse an inline style like a CSS class
- In real life: The paint covers the jacket's own color
- In JavaScript: An inline style beats normal stylesheet rules
There are two ways to write a CSS property name:
- With a dot, use camelCase: drop the dashes and start each later word with a capital letter, as in
style.backgroundColor. - With
setProperty, use the CSS spelling with dashes, called kebab-case, as instyle.setProperty("border-radius", "16px").
Two more rules:
- If a value is not valid CSS, the browser ignores it. You get no error.
- Most sizes need a unit, such as
px. A bare number like100is not a valid width.
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");A class gives the box its color. No inline style is set yet.
"100px", and the browser saves it.style.cssText is the whole style attribute as one string. Setting it replaces every inline style at once.
const box = document.querySelector(".box");
box.style.color = "crimson";
box.style.cssText = "background-color: gold; width: 100px;";
console.log(box.style.color);
console.log(box.style.width);- Line 2 sets an inline color,
crimson. - Line 3 replaces the whole inline style with a background and a width. The color is gone.
- Line 4 prints an empty line, because there is no inline color any more.
- Line 5 prints
100px.
Only use cssText when you really want to replace every inline style.
The table compares the four tools. Read each row as: "this tool is good for this, but not for that."
| Tool | Good for | Not good when |
|---|---|---|
classList | Looks you designed in CSS, such as open, active, error, or dark. | The value changes all the time, such as the mouse position. |
style.property | A normal CSS property, written in camelCase like backgroundColor. | The name has dashes, or it is a custom property. |
style.setProperty() | Names with dashes, custom properties, and the "important" flag. | A class would describe the state more clearly. |
style.cssText | Replacing every inline style on purpose. | You only meant to change one property. |
getComputedStyle
BROWSER PHOTOel.style sees only inline styles. getComputedStyle(el) gives you the final look instead. The browser works it out from every place a style can come from: stylesheet rules, classes, inline styles, inherited values, and browser defaults.
The values come back in one standard form, called resolved values. For example, colors often come back as rgb(...), and sizes come back in pixels.
When you need to know what the browser ended up using, take a photo. It helps most when a value came from a class or from a parent, not from an inline style.
- In real life: A photo taken after everyone got dressed
- In JavaScript:
getComputedStyle(element) - In real life: The photo shows what people actually wear
- In JavaScript: The final values after rules, inherited values, and defaults
- In real life: Drawing on the photo doesn't change the clothes
- In JavaScript: You can't change the page through the object it gives back
const computed = getComputedStyle(box);console.log(computed.color);console.log(computed.width);console.log(box.style.color);console.log(getComputedStyle(box, "::before").content);Nothing read yet.
Press Read computed style to read the box's final values.
getComputedStyle can make the browser recalculate styles. Read once, keep what you need, and don't call it again and again in a fast loop.box.style.color, is empty, while line 2, computed.color, gives the final color.A pseudo-element is a part of an element that CSS creates, such as ::before. To read its style, pass its name as the second argument: getComputedStyle(el, "::before").
Reading computed style can make the browser stop and recalculate the styles of the page first. So read it once, keep the values you need, and don't read it again and again inside a loop that runs many times a second, such as an animation.
CSS custom properties
THEME LABA custom property is a CSS property whose name you make up. The name starts with two dashes, such as --hue or --brand-color. A CSS rule reads it with var(--hue). Some facts about custom properties:
- The letter case matters:
--hueand--Hueare two different names. - Child elements get the value from their parent, like text color.
- You set one with
setPropertyand read it withgetPropertyValue. - The dot form can't write a name that starts with
--, sostyle.--huedoes not work.
A thermostat is the dial on the wall that sets the heat for the whole house. You turn it once, and every room follows. You don't walk to each heater and change it by hand.
- In real life: One thermostat setting
- In JavaScript: One value such as
--hue: 210 - In real life: Every room follows 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)
In code, JavaScript sets one custom property and lets CSS do the design work. JavaScript keeps the changing data. CSS decides how that data turns into colors and borders.
var(--hue). JavaScript changes the one value, and all three use it.: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 --hue back as waiting. You changed one value, and several CSS rules followed.
const panel = document.querySelector("#panel");
panel.style.setProperty("--hue", "320");
console.log(getComputedStyle(panel).getPropertyValue("--hue").trim());- Line 1 finds the panel.
- Line 2 sets
--hueto"320"on the panel. - Line 3 reads the final value back.
trim()removes any spaces around it. It prints320.
JavaScript chose the value. The CSS rule still decides how that hue becomes a background, a border, or a badge. Write the dashes and the letter case the same way in both places.
setProperty also takes a third argument, "important", as in setProperty("color", "red", "important"). It works like !important in CSS: a flag that makes a value win over almost every other rule. Use it rarely.
Newer browsers also let you give a custom property a type, such as a number or a color. Then the browser can animate it smoothly. In CSS you write @property; in JavaScript you call CSS.registerProperty. Check that the browser has the feature before you depend on it.
Where you will use this
SORT ITReal pages use all three tools. For example:
- A menu opens when JavaScript adds a class.
- A panel you drag follows the mouse through an inline
transformstyle. - A theme editor hands the color a user picked to CSS as a custom property.
- Turn on dark mode for the whole page
- Move a tooltip to follow the mouse
- Let a user pick a brand color
- Hide or show a finished item
- Set a progress bar to exactly 63%
- Make every card read
--hue - Mark one tab as selected
- Pass a slider value into several CSS rules
Pick the tool that fits each job best. A real app could sometimes use two; choose the usual first choice.
A dark-mode switch is a good job for a class. The code below does not set ten colors. It turns on one page state, and CSS holds all the dark colors.
const page = document.documentElement;
page.classList.toggle("theme-dark", true);
console.log(page.classList.contains("theme-dark"));- Line 1 gets the
<html>element, the root of the page. - Line 2 calls
togglewithtrue, sotheme-darkis always added, never removed. - Line 3 prints
true, because the page now has thetheme-darkclass.
toggle also gives back true or false. A real switch can use that to update its own label, such as "Dark mode: on".
Here is the lesson's rule in three small functions:
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));
}- Line 2 opens or closes the menu with a class. "Open" is a look designed in CSS.
- Line 6 moves the tooltip with an inline style. The numbers
xandychange every time the mouse moves. - Line 10 hands one hue to CSS as the custom property
--brand-hue.
Common misconceptions
- "
classNameandclassListstore classes in two different places." Both show the sameclassattribute. Change one, and the other changes too. - "
el.style.colortells me the color I see." It only tells you the inline color. UsegetComputedStyle(el).colorfor the final color. - "JavaScript adds the unit for me." It does not. A real element ignores
style.width = 100. Write"100px"or another size with a unit. - "Inline style is always bad." It is a poor choice for looks you reuse. It is a good choice for live numbers, such as the mouse position, a measured size, or progress.
- "Reading computed style costs nothing." It can make the browser recalculate styles first. Don't read it again and again inside a fast loop.
- "Custom properties are JavaScript variables." They are CSS properties. JavaScript can set them, but CSS decides what they change on the page.
- "Setting
cssTextadds one style." It replaces every inline style. Usestyle.setPropertywhen the other inline styles must stay. - "The class I add last always wins." CSS also looks at how exact each rule's selector is (called specificity) and at
!important. Adding a class can't beat a stronger rule. - "A custom property belongs to one element only." Child elements get it from their parent. Set it on a panel, and every card inside can read it, unless a card sets its own.
The table sums up each tool. Read each row as: "this tool reads this, writes that, and gives back this."
| Tool | What it reads | What it writes | What to remember |
|---|---|---|---|
className | The whole class string. | The whole class string. | It is one plain string. |
classList | The class names, one by one. | One class name at a time. | toggle gives back the new state; replace gives back whether it worked. |
el.style | Inline styles only. | Inline styles only. | A value that is not valid CSS is ignored. |
getComputedStyle | The final values the browser worked out. | Nothing. You only read from it. | The values come back as strings, like rgb(...) and 120px. |
| Custom properties | Through computed style or inline style. | With setProperty. | The letter case of the name matters. |
Practice exercises
5 EXERCISESThis code copies what classList does, using a plain Set. Type exactly what the last 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 list starts with card and active. Line 3 removes active. Line 4 adds hidden. Line 5 joins the names with a space, so line 6 prints card hidden.
What does this print? Then think: why can the text on the page 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 is an empty string, so the comparison prints true. A class can still make the text look colored, because el.style only shows inline styles.
Write the body of a helper that adds or removes the is-active class. Use no if statement.
function setActive(tab, active) {
// Add or remove the "is-active" class based on active.
}function setActive(tab, active) {
tab.classList.toggle("is-active", active);
}With a second argument, toggle stops flipping. true always adds the class, and false always removes it. It also gives back the new state, but this helper does not need it.
This code copies what classList.replace does. Predict the true or false it prints.
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 list has no missing, so delete gives back false and line 3 adds nothing. Line 4 prints false. The real classList.replace("missing", "flat") also gives back false.
Write the line that hands the number to 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));A custom property is set by its name, with setProperty. String(hue) turns the number into the string "280". CSS can then use var(--brand-hue) in many rules.
Which two things should this helper update when a tab becomes active? Name the class and the attribute, joined with and.
function showTab(tab, active) {
tab.classList.toggle("is-active", active);
tab.setAttribute("aria-selected", String(active));
}function showTab(tab, active) {
tab.classList.toggle("is-active", active);
tab.setAttribute("aria-selected", String(active));
}
const tab = document.querySelector("[role=tab]");
showTab(tab, true);
console.log(tab.className, tab.getAttribute("aria-selected"));The helper toggles is-active for the look, and writes the same value into aria-selected for screen readers. So people who see the page and people who use a screen reader get the same answer.
Check your understanding
7 QUESTIONSQuestion 1 of 9When is
classList.add('is-open')better than setting several inline styles?Choose an answer to see the explanation.
Question 2 of 9What does
el.style.colorread?Choose an answer to see the explanation.
Question 3 of 9This code copies what
classList.toggle('active')does, using aSet. What does it 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 9Which line sets the custom property
--brand-hue?Choose an answer to see the explanation.
Question 5 of 9Here
styleis a plain object, not a real element's style. What does it 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 9Which sentence describes
getComputedStyle(el)best?Choose an answer to see the explanation.
Question 7 of 9What does
classList.replace('old', 'new')give back whenoldis missing?Choose an answer to see the explanation.
Question 8 of 9Why should an active tab update both a class and
aria-selected?Choose an answer to see the explanation.
Question 9 of 9A user picks a hue. Which choice lets CSS keep deciding the design?
Choose an answer to see the explanation.
Key takeaways
classNameis the wholeclassattribute as one string.classListis a live list of the class names, with methods that change one name at a time.toggle(token, force)gives back the new state.replacegives back whether the swap worked.el.stylereads and writes inline styles only. The browser ignores a value that is not valid CSS, and you get no error.getComputedStylereads the final values. It can make the browser recalculate styles, so don't call it again and again in a fast loop.- A custom property hands one changing value from JavaScript to CSS, and many CSS rules can use it.
Final definition: styles and classes are the tools JavaScript uses to pick a look designed in CSS (a class), set a live value (an inline style or a custom property), and read the final look the browser worked out (getComputedStyle).
Up next: Sizes, scrolling & coordinates.