completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Styles & classes

Change how elements look from JavaScript with className, classList, inline styles, getComputedStyle, and CSS custom properties.

By the end you can
  • 01
    Choose 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.
  • 02
    Change classes safelyTell className (one string) from classList (a live list of names). Use toggle(token, force) and replace, and know what each gives back.
  • 03
    Read the final lookUse getComputedStyle to 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 class attribute. Each name picks rules from the stylesheet.
  • Inline styles: CSS written in the element's own style attribute.
  • 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.

The short version

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.

Real-life analogyClasses are outfits in a wardrobe

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, and theme-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.style can and cannot read,
  • read the final look with getComputedStyle,
  • change a whole theme with one custom property.

className and classList

INTERACTIVE

className 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:

Replace every classPop out in the code editor (opens in a new tab)JavaScript
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.

Change one class namePop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("button");
button.className = "button is-ready";
button.classList.add("is-busy");
button.classList.remove("is-ready");
console.log(button.className);
  1. Line 1 finds the button.
  2. Line 2 replaces the whole string with button is-ready.
  3. Line 3 adds one token, is-busy.
  4. Line 4 removes one token, is-ready.
  5. Line 5 prints button is-busy.
The class names after each line. Line 2, className =, gives button and is-ready. Line 3, add("is-busy"), gives button, is-ready and is-busy. Line 4, remove("is-ready"), gives button and is-busy. The class button stays the whole time.className =buttonis-readyadd("is-busy")buttonis-readyis-busyremove("is-ready")buttonis-busybutton stays
Line 2 sets the whole class string. Lines 3 and 4 each change one class name, so 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. With true it only adds; with false it only removes.
  • replace(oldToken, newToken) swaps one token for another.
  • contains(token) answers true or false: is the token there?
Replay class tokens
Step 0 of 9Ready
Your turn: follow the blue line

Step through a small copy of classList, built from a Set. The classList console below uses a real element.

Running in
  1. script
Next: line 1
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 4 and watch what toggle gives back

This step-through uses a plain Set, so it runs without a page. The classList console below uses a real element.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Now try the same methods on a real element. Watch what two of them give back:

  • toggle(token, force) gives back true if the token is there after the call, and false if it is not.
  • replace(oldToken, newToken) gives back true if the old token was there and got swapped, and false if it was missing.
classList console
Try these classList operationsPop out in the code editor (opens in a new tab)JavaScript
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);
Live elementDOMTokenList
classNamedemo-card is-muted
classListdemo-card | is-muted
Try it yourself

Ready. Press a button to run one method.

The card is a real element. Each button calls one classList method on it. The panel shows the class string and the list of names.
One token per argument

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.

Mark the chosen tab for eyes and screen readersPop out in the code editor (opens in a new tab)JavaScript
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"));
  1. Line 2 adds is-active when active is true, and removes it when it is false. The class changes how the tab looks.
  2. Line 3 writes the same true or false into aria-selected as a string. This tells the screen reader.
  3. 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

LAB

el.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.

Real-life analogyInline style is paint on the jacket

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 in style.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 like 100 is not a valid width.
Inline style playground
Read and write inline stylesPop out in the code editor (opens in a new tab)JavaScript
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");
Live style attributeinline only
style attribute
Try it yourself

A class gives the box its color. No inline style is set yet.

The browser ignores a CSS value that is not valid, and gives no error. That is why the width = 100 button seems to do nothing.
Left: box.style.width = 100. The style cart still holds width: empty string, because a width with no unit is ignored. Right: box.style.width = "100px". The style cart now holds width: 100px.style.width = 100width: ""styleignored: no unitstyle.width = "100px"width: 100pxstylesaved
Line 6 gives a number with no unit, so the browser ignores it and the width stays empty. Line 8 gives "100px", and the browser saves it.

style.cssText is the whole style attribute as one string. Setting it replaces every inline style at once.

cssText replaces every inline stylePop out in the code editor (opens in a new tab)JavaScript
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);
  1. Line 2 sets an inline color, crimson.
  2. Line 3 replaces the whole inline style with a background and a width. The color is gone.
  3. Line 4 prints an empty line, because there is no inline color any more.
  4. 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."

Choosing between classList and style
ToolGood forNot good when
classListLooks you designed in CSS, such as open, active, error, or dark.The value changes all the time, such as the mouse position.
style.propertyA 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.cssTextReplacing every inline style on purpose.You only meant to change one property.

getComputedStyle

BROWSER PHOTO

el.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.

Real-life analogyComputed style is a photo of the final outfit

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
Read the final style
Read the final valuesPop out in the code editor (opens in a new tab)JavaScript
const computed = getComputedStyle(box);console.log(computed.color);console.log(computed.width);console.log(box.style.color);console.log(getComputedStyle(box, "::before").content);
Resolved readingsbrowser

Nothing read yet.

Try it yourself

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.
Left: box.style.color gives an empty string, because it reads only the inline style. Right: computed.color gives rgb(102, 51, 153), the final purple color that came from the class.box.style.color""inline style onlycomputed.colorrgb(102, 51, 153)final color, from the class
In Edit & run, a class makes the box purple. So line 4, 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 LAB

A 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: --hue and --Hue are two different names.
  • Child elements get the value from their parent, like text color.
  • You set one with setProperty and read it with getPropertyValue.
  • The dot form can't write a name that starts with --, so style.--hue does not work.
Real-life analogyA custom property is a thermostat setting

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.

setProperty("--hue", "320") puts 320 into one box called --hue. Three arrows go from the box to three cards: page tint, card border, and badge. Each CSS rule reads the same --hue.setProperty("--hue", "320")320--huepage tintcard borderbadge
In the theme lab below, three CSS rules read var(--hue). JavaScript changes the one value, and all three use it.
Change one custom property
Set a custom property and read it backCSS + JavaScript
:root { --hue: 210; }.card { background: hsl(var(--hue) 80% 45%); } panel.style.setProperty("--hue", "320");const hue = getComputedStyle(panel).getPropertyValue("--hue").trim();
Sandboxed themeday
parent preview hue210
Try it yourself

The frame reads --hue back as waiting. You changed one value, and several CSS rules followed.

The preview runs in its own sandboxed frame with its own CSS, so it can't change this page. Three rules in it read var(--hue): the page tint, the card border, and the badge.
Read a custom property backPop out in the code editor (opens in a new tab)JavaScript
const panel = document.querySelector("#panel");
panel.style.setProperty("--hue", "320");
console.log(getComputedStyle(panel).getPropertyValue("--hue").trim());
  1. Line 1 finds the panel.
  2. Line 2 sets --hue to "320" on the panel.
  3. Line 3 reads the final value back. trim() removes any spaces around it. It prints 320.

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 IT

Real 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 transform style.
  • A theme editor hands the color a user picked to CSS as a custom property.
classList, style, or 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
Try it yourself
0 of 8 correct

Pick the tool that fits each job best. A real app could sometimes use two; choose the usual first choice.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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.

Turn on dark mode with one classPop out in the code editor (opens in a new tab)JavaScript
const page = document.documentElement;
page.classList.toggle("theme-dark", true);
console.log(page.classList.contains("theme-dark"));
  1. Line 1 gets the <html> element, the root of the page.
  2. Line 2 calls toggle with true, so theme-dark is always added, never removed.
  3. Line 3 prints true, because the page now has the theme-dark class.

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:

One function for each toolPop out in the code editor (opens in a new tab)JavaScript
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));
}
  1. Line 2 opens or closes the menu with a class. "Open" is a look designed in CSS.
  2. Line 6 moves the tooltip with an inline style. The numbers x and y change every time the mouse moves.
  3. Line 10 hands one hue to CSS as the custom property --brand-hue.

Common misconceptions

  • "className and classList store classes in two different places." Both show the same class attribute. Change one, and the other changes too.
  • "el.style.color tells me the color I see." It only tells you the inline color. Use getComputedStyle(el).color for 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 cssText adds one style." It replaces every inline style. Use style.setProperty when 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."

What each style tool reads and writes
ToolWhat it readsWhat it writesWhat to remember
classNameThe whole class string.The whole class string.It is one plain string.
classListThe class names, one by one.One class name at a time.toggle gives back the new state; replace gives back whether it worked.
el.styleInline styles only.Inline styles only.A value that is not valid CSS is ignored.
getComputedStyleThe final values the browser worked out.Nothing. You only read from it.The values come back as strings, like rgb(...) and 120px.
Custom propertiesThrough computed style or inline style.With setProperty.The letter case of the name matters.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict the class string

This code copies what classList does, using a plain Set. Type exactly what the last console.log prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
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);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWhy is the inline color empty?

    What does this print? Then think: why can the text on the page have a color while this code still prints true?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const el = { style: { color: "" } };
    console.log(el.style.color === "");

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite a helper that turns a tab on or off

      Write the body of a helper that adds or removes the is-active class. Use no if statement.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function setActive(tab, active) {
        // Add or remove the "is-active" class based on active.
      }
        Exercise 4 · PracticeWhat does replace give back when the old class is missing?

        This code copies what classList.replace does. Predict the true or false it prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const classes = new Set(["card", "raised"]);
        const replaced = classes.delete("missing");
        if (replaced) classes.add("flat");
        console.log(replaced);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeTheme a card with one custom property

          Write the line that hands the number to CSS.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const card = document.querySelector(".card");
          const hue = 280;
          // Set --brand-hue on card so CSS can use it.
            Exercise 6 · ChallengeUpdate both parts of an active tab

            Which two things should this helper update when a tab becomes active? Name the class and the attribute, joined with and.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            function showTab(tab, active) {
              tab.classList.toggle("is-active", active);
              tab.setAttribute("aria-selected", String(active));
            }

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              7 QUESTIONS
              Styles & classes quiz · 9 questionsScore: first tries count
              1. Question 1 of 9When is classList.add('is-open') better than setting several inline styles?

                Choose an answer to see the explanation.

              2. Question 2 of 9What does el.style.color read?

                Choose an answer to see the explanation.

              3. Question 3 of 9This code copies what classList.toggle('active') does, using a Set. What does it print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const 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.

              4. Question 4 of 9Which line sets the custom property --brand-hue?

                Choose an answer to see the explanation.

              5. Question 5 of 9Here style is a plain object, not a real element's style. What does it print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const style = { width: "" };
                style.width = String(100);
                console.log(style.width);

                Choose an answer to see the explanation.

              6. Question 6 of 9Which sentence describes getComputedStyle(el) best?

                Choose an answer to see the explanation.

              7. Question 7 of 9What does classList.replace('old', 'new') give back when old is missing?

                Choose an answer to see the explanation.

              8. Question 8 of 9Why should an active tab update both a class and aria-selected?

                Choose an answer to see the explanation.

              9. 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

              • className is the whole class attribute as one string. classList is a live list of the class names, with methods that change one name at a time.
              • toggle(token, force) gives back the new state. replace gives back whether the swap worked.
              • el.style reads and writes inline styles only. The browser ignores a value that is not valid CSS, and you get no error.
              • getComputedStyle reads 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.

              CompleteFrontend Clear concepts. Working examples.