Styles and classes

Before any of the JavaScript, one rule sets the whole chapter up. It reads as obvious once said, yet it decides how maintainable your UI code turns out.

An element can get its look from two places:

  1. A class defined in CSS, attached with <div class="...">.
  2. Inline properties, written straight into the element’s style attribute: <div style="...">.

JavaScript can drive both. It can flip classes on and off, and it can set inline style properties one by one.

The habit worth building: reach for a CSS class first, and only fall back to style when a class genuinely can’t express what you need.

Inline style earns its place when a value is computed at runtime and can’t be known ahead of time — element coordinates being the classic case:

let top = /* complex calculations */;
let left = /* complex calculations */;

elem.style.left = left; // e.g. '123px', figured out while running
elem.style.top = top;   // e.g. '456px'

Everything else — turning text red, dropping in a background icon, spacing something out — belongs in a stylesheet behind a class name. Then JavaScript’s whole job is to add or remove that class. The visual detail stays in CSS where a designer can find it, and your script stays about behavior.

the element<div>class=“box active”points to CSS rulesstyle=“left:123px”values written inlineJavaScript can editclassName / classListandelem.style.*
Two routes to an element's appearance. Classes point at rules that live in your CSS; inline style writes values directly onto the element. Prefer the left route.

className and classList

Toggling a class is one of the things scripts do most. The DOM gives you two handles for it, and they solve slightly different problems.

Start with the older one. Early JavaScript had a restriction: a reserved word like class couldn’t be used as an object property, so elem.class was off the table. That restriction is gone in modern JavaScript, but by the time it lifted the DOM had already settled on a workaround name. The property elem.className maps to the element’s class attribute.

Read it back and you get the whole class string:

<body class="panel open">
  <script>
    alert(document.body.className); // panel open
  </script>
</body>

The catch: assigning to className overwrites the entire string at once. Sometimes that’s exactly right — you’re setting a fresh, known set of classes. But often you only want to touch a single class and leave the rest alone. Do that through className and you’re stuck manually splitting the string, editing it, and joining it back.

That’s the job elem.classList exists for. It’s a special object with methods that add, remove, or toggle one class without disturbing the others.

<body class="panel open">
  <script>
    // add a class
    document.body.classList.add('pinned');

    alert(document.body.className); // panel open pinned
  </script>
</body>

So you have both handles. Use className when you want the full set as one string; use classList when you’re editing classes individually.

class attribute:“panel open pinned”classNameone string — read or replace all at onceclassListpanelopenpinnedadd / remove / toggle each token independently
className is the whole string in one piece. classList treats the same class attribute as a list of individual tokens you can add, remove, or toggle without touching the others.

The methods on classList:

  • elem.classList.add("class") / elem.classList.remove("class") — adds or removes the class.
  • elem.classList.toggle("class") — adds the class if it’s missing, removes it if it’s present. This is the one you’ll wire to a click handler again and again.
  • elem.classList.contains("class") — returns true or false depending on whether the class is there.

classList is also iterable, so a for..of loop walks every class name on the element:

<body class="panel open">
  <script>
    for (let name of document.body.classList) {
      alert(name); // panel, and then open
    }
  </script>
</body>

Here every button on classList in one place. Click Toggle a few times to watch the highlight class come and go, and notice how the read-out of className and contains stays in sync — the other classes on the card are never disturbed.

interactiveadd / remove / toggle / contains

Element style

The elem.style property is an object that mirrors the element’s style attribute. Writing elem.style.width = "100px" has the same effect as if the markup carried style="width:100px".

CSS property names are hyphenated; JavaScript property names can’t contain hyphens (a hyphen means subtraction). So each multi-word CSS property becomes camelCase on the style object — the hyphen drops out and the next letter goes uppercase:

background-color  => elem.style.backgroundColor
z-index           => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth

For example:

document.body.style.backgroundColor = prompt('background color?', 'teal');

Drag the sliders below. Each one writes a single camelCased property onto the box’s inline style, and the read-out shows the exact style string the DOM builds up as a result.

interactivewriting camelCased style properties

Resetting the style property

Sometimes you set a style property and later want it gone — not set to some other value, but unset, as if you’d never touched it.

Hiding an element is the everyday case: elem.style.display = "none".

To bring it back, you might reach for delete elem.style.display. Don’t. The right move is to assign an empty string: elem.style.display = "".

// run this and the <body> blinks
document.body.style.display = "none"; // hide

setTimeout(() => document.body.style.display = "", 1000); // back to normal

Setting style.display to "" clears that inline property. With the inline value gone, the element falls back to whatever its CSS classes and the browser’s built-in styles say — exactly as if the inline property had never been there.

There’s also a dedicated method, elem.style.removeProperty('propertyName'), which clears a single property:

document.body.style.background = 'red'; // set background to red

setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove it after 1 second

Mind the units

CSS values often need a unit, and style won’t add one for you. Assign a bare number where CSS expects a length and the browser silently ignores it.

<body>
  <script>
    // doesn't work!
    document.body.style.margin = 16;
    alert(document.body.style.margin); // '' (empty string — the assignment was ignored)

    // add the unit and it works
    document.body.style.margin = '16px';
    alert(document.body.style.margin); // 16px

    alert(document.body.style.marginTop);  // 16px
    alert(document.body.style.marginLeft); // 16px
  </script>
</body>

Notice the last two reads. You set the shorthand style.margin, and the browser unpacks it into the longhand pieces — marginTop, marginLeft, and the rest — so each one reads back the value derived from the shorthand.

Computed styles: getComputedStyle

Writing styles is the easy half. Reading them back has a trap that catches almost everyone once.

Say you want an element’s real size, its margins, its color. Your instinct is to read elem.style.something. That works only for values written into the inline style attribute.

elem.style reflects the style attribute alone — nothing from the CSS cascade.

So anything an element inherited from a class or a stylesheet rule is invisible to elem.style. Here the margin and color come from a <style> block, and style reports them as empty:

<head>
  <style> body { color: blue; margin: 8px } </style>
</head>
<body>

  The blue text
  <script>
    alert(document.body.style.color);     // empty
    alert(document.body.style.marginTop); // empty
  </script>
</body>

To read the value the element actually ended up with — after every class, rule, and default has been resolved — use getComputedStyle.

The syntax:

getComputedStyle(element, [pseudo])
element

The element whose value you want to read.

pseudo

A pseudo-element, if you need one — for instance ::before. Leave it out (or pass an empty string) to read the element itself.

You get back a style-like object, same shape as elem.style, but this one accounts for every CSS rule that applied:

<head>
  <style> body { color: blue; margin: 8px } </style>
</head>
<body>

  <script>
    let computedStyle = getComputedStyle(document.body);

    // now the margin and color are readable

    alert( computedStyle.marginTop ); // 8px
    alert( computedStyle.color );     // rgb(0, 0, 255)
  </script>

</body>

The demo below makes the split concrete. The paragraph gets its color from a stylesheet rule and its padding from an inline attribute. Click the button and compare the two columns: elem.style only knows about the inline padding, while getComputedStyle reports the resolved value of everything.

interactiveelem.style vs getComputedStyle
stylesheet rulebody { margin:8px }inline attributestyle=“…”CSS cascaderesolves it allelem.styleinline only → ‘’getComputedStylefinal → 8px
Why one read is empty and the other isn't. elem.style sees only the inline attribute; getComputedStyle sees the finished result after the whole cascade runs.

Summary

Two DOM properties manage classes:

  • className — the class attribute as a single string. Reach for it when you want to read or replace the whole set at once.
  • classList — an object with add / remove / toggle / contains, plus iteration. Reach for it when you’re editing individual classes.

Two ways to change styles:

  • style — an object of camelCased properties that mirrors the inline style attribute. Reading and writing it is the same as editing that attribute. For !important and the rarer operations, the CSSStyleDeclaration reference on MDN lists the methods.
  • style.cssText — the entire style attribute as one string. Assigning it replaces everything.

One way to read the finished styles, after all CSS has applied and values are resolved:

  • getComputedStyle(elem, [pseudo]) — returns a read-only, style-like object of the resolved values.