Skip to main content

How to Work with Styles and Classes in JavaScript

JavaScript gives you full control over how elements look on the page. You can add and remove CSS classes, set inline styles, read the final computed values the browser applies, and even manipulate CSS custom properties (variables) at runtime. These capabilities power every dynamic visual effect you see on the web: theme switching, animations triggered by user actions, responsive layout adjustments, and interactive UI state changes.

There are two fundamentally different approaches to styling elements with JavaScript: modifying CSS classes and setting inline styles directly. Knowing when to use each approach, and how to correctly read style values back, is essential for writing clean, maintainable, and performant front-end code.

This guide covers everything from the basic className and classList APIs through inline style manipulation with element.style and cssText, reading final computed styles with getComputedStyle, handling CSS units properly, and dynamically controlling CSS custom properties.

className: The String of Classes

Every DOM element has a className property that reflects the HTML class attribute as a single string. It gives you the full class list as one string, with class names separated by spaces.

Reading className

<div id="box" class="card featured active">Content</div>
let box = document.getElementById("box");
console.log(box.className); // "card featured active"
console.log(typeof box.className); // "string"
info

The property is called className instead of class because class is a reserved keyword in JavaScript (used for class declarations). The HTML attribute is class, but the DOM property is className.

Writing className

Setting className replaces the entire class string:

let box = document.getElementById("box");

// Replace ALL classes
box.className = "card highlighted";
console.log(box.className); // "card highlighted"
// "featured" and "active" are gone!

// Remove all classes
box.className = "";
console.log(box.className); // ""

// Set a single class
box.className = "container";
console.log(box.className); // "container"

The Problem with className for Single Class Changes

Because className is a single string, adding or removing one class while preserving others requires string manipulation:

let box = document.getElementById("box");
// Current: "card featured active"

// Adding a class with className (awkward)
box.className += " highlighted";
// "card featured active highlighted"

// Removing a class with className (fragile and error-prone)
box.className = box.className
.split(" ")
.filter(c => c !== "featured")
.join(" ");
// "card active highlighted"

This approach is verbose, fragile, and can introduce bugs (double spaces, accidentally removing partial matches). This is exactly why classList was introduced.

When className Is Still Useful

className is useful when you want to replace all classes at once:

// Set the complete class list in one operation
element.className = "modal open centered";

// Clear all classes
element.className = "";

// Copy classes from one element to another
targetElement.className = sourceElement.className;

classList: The Modern Class API

The classList property returns a live DOMTokenList object that provides methods for adding, removing, toggling, and checking individual classes. It is the standard way to work with CSS classes in modern JavaScript.

classList.add(...classes)

Adds one or more classes. If a class already exists, it is silently ignored (no duplicates).

let box = document.getElementById("box");

// Add a single class
box.classList.add("highlighted");

// Add multiple classes at once
box.classList.add("rounded", "shadow", "animated");

// Adding a class that already exists does nothing
box.classList.add("highlighted"); // No duplicate added

console.log(box.className);
// "card featured active highlighted rounded shadow animated"

classList.remove(...classes)

Removes one or more classes. If a class does not exist, it is silently ignored (no error).

let box = document.getElementById("box");

// Remove a single class
box.classList.remove("active");

// Remove multiple classes at once
box.classList.remove("shadow", "animated");

// Removing a non-existent class does nothing
box.classList.remove("nonexistent"); // No error

console.log(box.className);
// "card featured highlighted rounded"

classList.toggle(class, force?)

If the class exists, it is removed. If it does not exist, it is added. Returns true if the class is now present, false if it was removed.

let box = document.getElementById("box");

// Toggle "active": adds it (wasn't present after previous removal)
let result = box.classList.toggle("active");
console.log(result); // true (class was added)
console.log(box.classList.contains("active")); // true

// Toggle "active" again: removes it
result = box.classList.toggle("active");
console.log(result); // false (class was removed)
console.log(box.classList.contains("active")); // false

The optional second parameter force controls the behavior:

  • true: always add the class (like add)
  • false: always remove the class (like remove)
let isLoggedIn = true;

// force = true → always add
box.classList.toggle("authenticated", isLoggedIn);
// Same as: if (isLoggedIn) box.classList.add("authenticated")
// else box.classList.remove("authenticated")

let isDarkMode = false;

// force = false → always remove
box.classList.toggle("dark-theme", isDarkMode);

This force parameter is particularly useful when you want to set a class based on a condition without writing if/else:

// Instead of this:
if (isActive) {
element.classList.add("active");
} else {
element.classList.remove("active");
}

// Write this:
element.classList.toggle("active", isActive);

classList.contains(class)

Returns true if the element has the specified class, false otherwise.

let box = document.getElementById("box");

console.log(box.classList.contains("card")); // true
console.log(box.classList.contains("highlighted")); // true
console.log(box.classList.contains("nonexistent")); // false

Common use: conditional logic based on class presence.

let menu = document.getElementById("menu");

if (menu.classList.contains("open")) {
console.log("Menu is open");
menu.classList.remove("open");
} else {
console.log("Menu is closed");
menu.classList.add("open");
}

// Or simply:
menu.classList.toggle("open");

classList.replace(oldClass, newClass)

Replaces one class with another. Returns true if the old class was found and replaced, false if the old class was not found.

let button = document.querySelector("button");

// Change from "btn-primary" to "btn-danger"
let replaced = button.classList.replace("btn-primary", "btn-danger");
console.log(replaced); // true (if btn-primary existed)

// If the old class doesn't exist, nothing happens
let result = button.classList.replace("nonexistent", "btn-info");
console.log(result); // false

Iterating Over Classes

classList is iterable. You can loop over it and access individual classes by index:

let box = document.getElementById("box");

// for...of
for (let cls of box.classList) {
console.log(cls);
}
// "card"
// "featured"
// "highlighted"
// "rounded"

// forEach
box.classList.forEach(cls => console.log(cls));

// By index
console.log(box.classList[0]); // "card"
console.log(box.classList[1]); // "featured"
console.log(box.classList.length); // 4

// Convert to array
let classArray = Array.from(box.classList);
// or
let classArray2 = [...box.classList];
console.log(classArray); // ["card", "featured", "highlighted", "rounded"]

classList Quick Reference

MethodDescriptionReturns
add(c1, c2, ...)Add one or more classesundefined
remove(c1, c2, ...)Remove one or more classesundefined
toggle(c, force?)Toggle a class on/offboolean (is now present?)
contains(c)Check if class existsboolean
replace(old, new)Replace one class with anotherboolean (was replaced?)
item(index)Get class by indexstring or null
lengthNumber of classesnumber

element.style: Inline Styles as Properties

The style property of a DOM element gives you access to the element's inline styles (the styles set directly on the element via the style HTML attribute or via JavaScript). It is a CSSStyleDeclaration object with one property for each CSS property.

Setting Inline Styles

CSS property names are converted to camelCase in JavaScript:

CSS PropertyJavaScript Property
background-colorstyle.backgroundColor
font-sizestyle.fontSize
border-radiusstyle.borderRadius
z-indexstyle.zIndex
margin-topstyle.marginTop
paddingstyle.padding
let box = document.getElementById("box");

box.style.backgroundColor = "#3498db";
box.style.color = "white";
box.style.padding = "20px";
box.style.borderRadius = "8px";
box.style.fontSize = "18px";
box.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";

The values must be strings, including their CSS units:

// ✅ Correct: values are strings with units
box.style.width = "200px";
box.style.marginTop = "1.5rem";
box.style.opacity = "0.8"; // No unit for opacity
box.style.zIndex = "100"; // No unit for z-index

// ❌ Wrong: missing units or wrong types
box.style.width = 200; // Sets to "200": no unit, usually ignored
box.style.marginTop = 1.5; // Sets to "1.5": no unit, ignored

Reading Inline Styles

element.style only returns values that are set inline (either in the HTML style attribute or via JavaScript). It does not return styles from CSS stylesheets, inherited styles, or computed styles:

<style>
#box { color: red; font-size: 20px; }
</style>

<div id="box" style="background: blue;">Content</div>
let box = document.getElementById("box");

// ✅ Returns inline styles
console.log(box.style.background); // "blue" (set inline in HTML)

// ❌ Returns empty string: these are from the stylesheet, not inline
console.log(box.style.color); // "" (not "red")
console.log(box.style.fontSize); // "" (not "20px")

This is a critical distinction. To read the final computed value of a CSS property, you need getComputedStyle (covered later).

Removing an Inline Style

To remove an inline style, set it to an empty string. This removes the property from the element's style attribute, allowing the CSS stylesheet value to take effect:

let box = document.getElementById("box");

// Set an inline style
box.style.color = "blue";

// Remove the inline style (the stylesheet/default value applies again)
box.style.color = "";

You can also use removeProperty:

box.style.removeProperty("color");
box.style.removeProperty("background-color"); // Use the CSS property name (with hyphens)

Vendor-Prefixed Properties

For vendor-prefixed CSS properties, the prefix is included in the camelCase name:

// -webkit-transform → webkitTransform
element.style.webkitTransform = "rotate(45deg)";

// -moz-appearance → MozAppearance (capital M for Mozilla)
element.style.MozAppearance = "none";

// The standard property (no prefix) is always preferred
element.style.transform = "rotate(45deg)";

Accessing Properties with Hyphens

If you need to use the CSS property name (with hyphens), you can use bracket notation:

// Both are equivalent:
element.style.backgroundColor = "red";
element.style["background-color"] = "red";

// setProperty also uses the CSS property name
element.style.setProperty("background-color", "red");

style.cssText: Setting Full Inline Style Strings

The cssText property lets you get or set the entire inline style as a single string. This is useful when you want to apply multiple styles at once.

Setting Multiple Styles at Once

let box = document.getElementById("box");

box.style.cssText = `
background-color: #3498db;
color: white;
padding: 20px;
border-radius: 8px;
font-size: 18px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
`;

Reading cssText

let box = document.getElementById("box");
box.style.backgroundColor = "red";
box.style.padding = "10px";

console.log(box.style.cssText);
// "background-color: red; padding: 10px;"

Warning: cssText Replaces Everything

Setting cssText replaces all existing inline styles, not just the ones you specify:

let box = document.getElementById("box");

// Set initial styles
box.style.color = "red";
box.style.fontSize = "20px";

// This REPLACES all inline styles: color and fontSize are gone!
box.style.cssText = "padding: 10px;";

console.log(box.style.color); // "" (gone!)
console.log(box.style.fontSize); // "" (gone!)
console.log(box.style.padding); // "10px"

If you want to add to existing inline styles without losing them, use individual style properties or setAttribute:

// Add to existing styles using setAttribute
// (this also replaces, but you can read first)
let currentStyle = box.getAttribute("style") || "";
box.setAttribute("style", currentStyle + "padding: 10px;");

// Better: use individual properties
box.style.padding = "10px"; // Does not affect other inline styles

When to Use cssText

cssText is best when:

  • You want to set many styles at once and there are no existing inline styles you need to preserve
  • You are initializing a newly created element's styles
  • You want to clear all inline styles (element.style.cssText = "")
// Good use: styling a freshly created element
let tooltip = document.createElement("div");
tooltip.style.cssText = `
position: absolute;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
pointer-events: none;
z-index: 1000;
`;
tip

In most cases, applying a CSS class is preferable to setting inline styles. Classes are reusable, easier to maintain, and keep styling logic in CSS where it belongs. Use inline styles for truly dynamic values that cannot be predefined in CSS (positions, dimensions calculated at runtime, etc.).

// ❌ Harder to maintain, mixes style logic into JS
element.style.cssText = "background: blue; color: white; padding: 20px;";

// ✅ Cleaner: define styles in CSS, toggle a class
element.classList.add("highlighted");
// .highlighted { background: blue; color: white; padding: 20px; }

getComputedStyle(element): Reading Final CSS Values

As we saw, element.style only reads inline styles. To read the final, computed value that the browser actually applies to an element (after combining stylesheets, inheritance, defaults, and inline styles), use getComputedStyle.

Basic Usage

<style>
#box {
color: red;
font-size: 20px;
padding: 15px;
margin: 10px auto;
width: 50%;
}
</style>

<div id="box">Content</div>
let box = document.getElementById("box");
let computed = getComputedStyle(box);

// Now we can read the actual applied values
console.log(computed.color); // "rgb(255, 0, 0)" (resolved to RGB)
console.log(computed.fontSize); // "20px" (resolved to absolute units)
console.log(computed.padding); // Might be "15px" or individual values
console.log(computed.paddingTop); // "15px" (always specific)
console.log(computed.width); // "400px" (computed from 50% of parent)
console.log(computed.margin); // "10px auto" or individual values
console.log(computed.display); // "block" (default for div)

getComputedStyle Returns Resolved Values

The returned values are resolved (also called computed or used values). This means:

  • Relative units (em, rem, %, vw) are converted to absolute pixels
  • Colors are converted to rgb() or rgba() format
  • Shorthand properties may or may not be available (use longhand properties to be safe)
  • Default/inherited values are included
let box = document.getElementById("box");
let computed = getComputedStyle(box);

// Colors are resolved to rgb/rgba
console.log(computed.color); // "rgb(255, 0, 0)" (not "red")
console.log(computed.backgroundColor); // "rgba(0, 0, 0, 0)" (transparent)

// Relative values are computed to pixels
console.log(computed.fontSize); // "20px" (even if set as "1.25rem" in CSS)
console.log(computed.width); // "400px" (even if set as "50%")

// Inherited values appear
console.log(computed.fontFamily); // "Times New Roman" or whatever is inherited

Reading Pseudo-Element Styles

getComputedStyle can also read styles from pseudo-elements by passing a second argument:

<style>
#box::before {
content: "→ ";
color: blue;
font-weight: bold;
}
</style>

<div id="box">Content</div>
let box = document.getElementById("box");
let beforeStyles = getComputedStyle(box, "::before");

console.log(beforeStyles.content); // '"→ "' (includes quotes)
console.log(beforeStyles.color); // "rgb(0, 0, 255)"
console.log(beforeStyles.fontWeight); // "700" (bold resolved to numeric)

The Result Is Read-Only

The CSSStyleDeclaration returned by getComputedStyle is read-only. You cannot set values on it:

let computed = getComputedStyle(box);

// ❌ This throws an error or is silently ignored
computed.color = "blue"; // Cannot set: it's read-only

// ✅ To change styles, use element.style or classList
box.style.color = "blue";

Use Longhand Properties, Not Shorthand

Shorthand properties like margin, padding, border, background, and font may not return values from getComputedStyle consistently across browsers. Always use longhand (specific) properties:

let computed = getComputedStyle(box);

// ❌ Shorthand: may return empty string or inconsistent values
console.log(computed.margin); // May be "" in some browsers
console.log(computed.background); // May be very complex or empty

// ✅ Longhand: always reliable
console.log(computed.marginTop); // "10px"
console.log(computed.marginRight); // "20px"
console.log(computed.marginBottom); // "10px"
console.log(computed.marginLeft); // "20px"
console.log(computed.backgroundColor); // "rgb(255, 255, 255)"

Practical Example: Reading and Comparing Styles

function isElementVisible(element) {
let computed = getComputedStyle(element);
return computed.display !== "none"
&& computed.visibility !== "hidden"
&& computed.opacity !== "0";
}

function getElementDimensions(element) {
let computed = getComputedStyle(element);
return {
width: parseFloat(computed.width),
height: parseFloat(computed.height),
paddingTop: parseFloat(computed.paddingTop),
paddingBottom: parseFloat(computed.paddingBottom),
marginTop: parseFloat(computed.marginTop),
marginBottom: parseFloat(computed.marginBottom)
};
}

let box = document.getElementById("box");
console.log(isElementVisible(box)); // true
console.log(getElementDimensions(box));
// { width: 400, height: 50, paddingTop: 15, paddingBottom: 15, marginTop: 10, marginBottom: 10 }

element.style vs. getComputedStyle: Summary

Featureelement.stylegetComputedStyle(element)
Reads inline stylesYesYes (among all sources)
Reads stylesheet stylesNoYes
Reads inherited stylesNoYes
Reads default stylesNoYes
WritableYesNo (read-only)
Values resolved to pxNo (returns what you set)Yes
Can read pseudo-elementsNoYes (::before, ::after)

CSS Units: Reading and Writing

Working with CSS values in JavaScript requires careful handling of units. CSS values from the DOM are always strings, and you often need to parse them to numbers or construct them with the correct unit.

Writing Values with Units

When setting styles via JavaScript, always include the unit as part of the string:

let box = document.getElementById("box");

// ✅ Include units
box.style.width = "200px";
box.style.fontSize = "1.5rem";
box.style.marginTop = "2em";
box.style.height = "50vh";
box.style.maxWidth = "80%";

// ✅ Unitless properties (these don't need units)
box.style.opacity = "0.8";
box.style.zIndex = "100";
box.style.flexGrow = "1";
box.style.lineHeight = "1.5";
box.style.order = "2";

// ❌ Missing units: the browser ignores these
box.style.width = "200"; // Ignored (no unit)
box.style.width = 200; // Coerced to string "200" (still no unit, ignored)

Parsing Numeric Values from Computed Styles

getComputedStyle returns strings like "20px", "1.5", "rgb(255, 0, 0)". To use these as numbers, you need to parse them:

let computed = getComputedStyle(box);

// parseFloat extracts the leading number, ignoring the unit
let width = parseFloat(computed.width); // 200 (from "200px")
let fontSize = parseFloat(computed.fontSize); // 20 (from "20px")
let opacity = parseFloat(computed.opacity); // 0.8 (from "0.8")

console.log(width); // 200 (number)
console.log(fontSize); // 20 (number)
console.log(opacity); // 0.8 (number)

// parseInt also works but truncates decimals
let marginTop = parseInt(computed.marginTop, 10); // 10 (from "10.5px" → 10)

Performing Calculations and Setting Results

A common pattern is reading a value, performing arithmetic, and setting the result:

let box = document.getElementById("box");
let computed = getComputedStyle(box);

// Double the current width
let currentWidth = parseFloat(computed.width);
box.style.width = (currentWidth * 2) + "px";

// Add 20px to the current margin
let currentMargin = parseFloat(computed.marginTop);
box.style.marginTop = (currentMargin + 20) + "px";

// Decrease opacity by 0.1
let currentOpacity = parseFloat(computed.opacity);
box.style.opacity = String(Math.max(0, currentOpacity - 0.1));

Template Literals for Clean Value Construction

let size = 200;
let unit = "px";

box.style.width = `${size}${unit}`;
box.style.height = `${size / 2}${unit}`;
box.style.transform = `translateX(${size}px) rotate(${45}deg)`;
box.style.transition = `all ${0.3}s ease-in-out`;
box.style.boxShadow = `0 ${4}px ${8}px rgba(0, 0, 0, ${0.2})`;

CSS Custom Properties (Variables) via JavaScript

CSS custom properties (also known as CSS variables) are a powerful way to create dynamic, themeable styles. JavaScript can read and write these variables at runtime, making them the bridge between JavaScript logic and CSS styling.

CSS Custom Properties Recap

Custom properties are defined with a -- prefix and accessed with var():

:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--font-size-base: 16px;
--spacing-unit: 8px;
--border-radius: 4px;
}

.card {
background-color: var(--primary-color);
font-size: var(--font-size-base);
padding: calc(var(--spacing-unit) * 2);
border-radius: var(--border-radius);
}

Reading CSS Custom Properties

Use getComputedStyle and getPropertyValue to read custom properties:

// Read from the root element (:root)
let root = document.documentElement;
let rootStyles = getComputedStyle(root);

let primaryColor = rootStyles.getPropertyValue("--primary-color");
console.log(primaryColor); // " #3498db" (may have leading space)
console.log(primaryColor.trim()); // "#3498db"

let fontSize = rootStyles.getPropertyValue("--font-size-base");
console.log(fontSize.trim()); // "16px"

You can also read custom properties from specific elements:

let card = document.querySelector(".card");
let cardStyles = getComputedStyle(card);

// This reads the computed value, which may come from :root or from the element itself
let bgColor = cardStyles.getPropertyValue("--primary-color").trim();
console.log(bgColor); // "#3498db"
caution

Note that getPropertyValue often returns a value with a leading space. Always use .trim() when reading custom property values to avoid unexpected whitespace issues.

Writing CSS Custom Properties

Use element.style.setProperty to set custom properties:

let root = document.documentElement;

// Set on :root (affects the entire document)
root.style.setProperty("--primary-color", "#e74c3c");
root.style.setProperty("--font-size-base", "18px");
root.style.setProperty("--spacing-unit", "12px");

// All elements using these variables update immediately!

You can also set custom properties on specific elements to create local overrides:

let card = document.querySelector(".card");

// Override --primary-color for this card only
card.style.setProperty("--primary-color", "#9b59b6");
// This card is now purple while others remain the global color

Removing CSS Custom Properties

let root = document.documentElement;

// Remove the inline custom property (the stylesheet value applies again)
root.style.removeProperty("--primary-color");

Practical Example: Theme Switcher

<style>
:root {
--bg-color: #ffffff;
--text-color: #333333;
--primary: #3498db;
--card-bg: #f8f9fa;
--shadow: 0 2px 4px rgba(0,0,0,0.1);
}

body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: system-ui, sans-serif;
transition: background-color 0.3s, color 0.3s;
}

.card {
background: var(--card-bg);
box-shadow: var(--shadow);
padding: 20px;
border-radius: 8px;
margin: 16px 0;
}

button {
background: var(--primary);
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
</style>

<button id="theme-toggle">Toggle Dark Mode</button>
<div class="card">
<h2>Card Title</h2>
<p>Card content goes here.</p>
</div>
let root = document.documentElement;
let themeBtn = document.getElementById("theme-toggle");

const themes = {
light: {
"--bg-color": "#ffffff",
"--text-color": "#333333",
"--primary": "#3498db",
"--card-bg": "#f8f9fa",
"--shadow": "0 2px 4px rgba(0,0,0,0.1)"
},
dark: {
"--bg-color": "#1a1a2e",
"--text-color": "#e0e0e0",
"--primary": "#e94560",
"--card-bg": "#16213e",
"--shadow": "0 2px 4px rgba(0,0,0,0.4)"
}
};

let currentTheme = "light";

themeBtn.addEventListener("click", () => {
currentTheme = currentTheme === "light" ? "dark" : "light";

let theme = themes[currentTheme];
for (let [property, value] of Object.entries(theme)) {
root.style.setProperty(property, value);
}

themeBtn.textContent = currentTheme === "light"
? "Toggle Dark Mode"
: "Toggle Light Mode";
});

Every element on the page that uses these custom properties updates instantly when the variables change. No need to query individual elements or manually change their styles.

Dynamic Styling with Custom Properties

Custom properties are particularly powerful for values that need to change based on user interaction or JavaScript calculations:

// Track mouse position and expose as CSS variables
document.addEventListener("mousemove", (event) => {
root.style.setProperty("--mouse-x", event.clientX + "px");
root.style.setProperty("--mouse-y", event.clientY + "px");
});
/* CSS can now use the mouse position */
.spotlight {
background: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
rgba(255,255,255,0.2) 0%,
transparent 50%
);
}
// Expose scroll progress as a CSS variable
window.addEventListener("scroll", () => {
let scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);
root.style.setProperty("--scroll-progress", scrollPercent);
});
/* Progress bar that tracks scroll position */
.progress-bar {
transform: scaleX(var(--scroll-progress));
transform-origin: left;
}

setProperty with Priority

The setProperty method accepts a third argument for priority, which lets you set !important:

// Set with !important
element.style.setProperty("color", "red", "important");

// This is equivalent to: element style="color: red !important;"

// Remove !important by setting without priority
element.style.setProperty("color", "red", "");
// or simply:
element.style.setProperty("color", "red");

Putting It All Together: Style Management Patterns

Pattern 1: State-Based Class Management

The most maintainable approach for most styling needs:

// Define states as CSS classes
function setLoadingState(container) {
container.classList.add("loading");
container.classList.remove("error", "success");
}

function setSuccessState(container) {
container.classList.remove("loading", "error");
container.classList.add("success");
}

function setErrorState(container) {
container.classList.remove("loading", "success");
container.classList.add("error");
}

Pattern 2: Dynamic Inline Styles for Calculated Values

Use inline styles when values depend on JavaScript calculations:

function positionTooltip(tooltip, target) {
let rect = target.getBoundingClientRect();

tooltip.style.position = "fixed";
tooltip.style.left = `${rect.left + rect.width / 2}px`;
tooltip.style.top = `${rect.top - 10}px`;
tooltip.style.transform = "translate(-50%, -100%)";
}

Pattern 3: CSS Variables for Theme and Configuration

Use custom properties when multiple elements need to react to the same value:

function setAccentColor(color) {
document.documentElement.style.setProperty("--accent", color);
// Every element using var(--accent) updates automatically
}

function setFontScale(scale) {
document.documentElement.style.setProperty("--font-scale", scale);
// CSS: font-size: calc(16px * var(--font-scale));
}

Choosing the Right Approach

ScenarioRecommended Approach
Toggling visual states (active, disabled, open)classList.toggle()
Applying a predefined lookclassList.add()
Positioning elements dynamicallyelement.style.*
Values calculated from JS (scroll, mouse, data)CSS custom properties or inline styles
Theming / global style changesCSS custom properties
Reading current visual stategetComputedStyle()
Initializing a new element's appearancestyle.cssText or classList.add()

Summary

JavaScript provides multiple ways to manage how elements look on the page. Each serves a different purpose.

CSS Classes (className and classList):

  • className gets or sets the full class string. Setting it replaces all classes.
  • classList provides add, remove, toggle, contains, replace for precise single-class operations.
  • Prefer classList in almost every situation. Use className only when replacing all classes at once.
  • Classes are the best approach for most styling needs: reusable, maintainable, and keeps style logic in CSS.

Inline Styles (element.style):

  • CSS property names use camelCase in JavaScript (backgroundColor, fontSize).
  • Values must be strings with appropriate units ("200px", "1.5rem").
  • Setting a property to "" (empty string) removes that inline style.
  • element.style only reads and writes inline styles, not stylesheet styles.
  • style.cssText sets the entire inline style string at once (replaces all existing inline styles).

Computed Styles (getComputedStyle):

  • Returns the final, resolved values the browser actually applies.
  • Combines stylesheets, inheritance, defaults, and inline styles.
  • Values are resolved to absolute units (pixels, rgb() colors, etc.).
  • Read-only. Use longhand properties for reliable results.
  • Can read pseudo-element styles with the second argument.

CSS Custom Properties:

  • Read with getComputedStyle(el).getPropertyValue("--name").
  • Write with element.style.setProperty("--name", value).
  • Remove with element.style.removeProperty("--name").
  • Set on :root (via document.documentElement) for global effect.
  • Set on specific elements for local overrides.
  • All elements using var(--name) update instantly when the variable changes.

Table of Contents