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"
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 (likeadd)false: always remove the class (likeremove)
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
| Method | Description | Returns |
|---|---|---|
add(c1, c2, ...) | Add one or more classes | undefined |
remove(c1, c2, ...) | Remove one or more classes | undefined |
toggle(c, force?) | Toggle a class on/off | boolean (is now present?) |
contains(c) | Check if class exists | boolean |
replace(old, new) | Replace one class with another | boolean (was replaced?) |
item(index) | Get class by index | string or null |
length | Number of classes | number |
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 Property | JavaScript Property |
|---|---|
background-color | style.backgroundColor |
font-size | style.fontSize |
border-radius | style.borderRadius |
z-index | style.zIndex |
margin-top | style.marginTop |
padding | style.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;
`;
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()orrgba()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
| Feature | element.style | getComputedStyle(element) |
|---|---|---|
| Reads inline styles | Yes | Yes (among all sources) |
| Reads stylesheet styles | No | Yes |
| Reads inherited styles | No | Yes |
| Reads default styles | No | Yes |
| Writable | Yes | No (read-only) |
| Values resolved to px | No (returns what you set) | Yes |
| Can read pseudo-elements | No | Yes (::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"
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
| Scenario | Recommended Approach |
|---|---|
| Toggling visual states (active, disabled, open) | classList.toggle() |
| Applying a predefined look | classList.add() |
| Positioning elements dynamically | element.style.* |
| Values calculated from JS (scroll, mouse, data) | CSS custom properties or inline styles |
| Theming / global style changes | CSS custom properties |
| Reading current visual state | getComputedStyle() |
| Initializing a new element's appearance | style.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):
classNamegets or sets the full class string. Setting it replaces all classes.classListprovidesadd,remove,toggle,contains,replacefor precise single-class operations.- Prefer
classListin almost every situation. UseclassNameonly 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.styleonly reads and writes inline styles, not stylesheet styles.style.cssTextsets 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(viadocument.documentElement) for global effect. - Set on specific elements for local overrides.
- All elements using
var(--name)update instantly when the variable changes.