What Is It?
What Is CSS Manipulation with JavaScript?
CSS Manipulation means using JavaScript to dynamically change the appearance of elements -- colors, sizes, visibility, positions, animations -- in response to user actions or application state. You have already used element.style and classList in previous chapters. This chapter goes deeper into all the techniques for controlling CSS from JavaScript.
There are three main approaches:
- Inline styles (
element.style.property) -- directly set CSS on an element - CSS classes (
element.classList) -- toggle predefined CSS classes - CSS custom properties (
setProperty) -- change CSS variables from JS
// Approach 1: Inline style
element.style.backgroundColor = "red";
// Approach 2: Toggle a class
element.classList.toggle("active");
// Approach 3: CSS variable
document.documentElement.style.setProperty("--primary-color", "#a855f7");
Why Does It Matter?
Why Learn CSS Manipulation?
1. Every Modern Feature Uses It
Dark mode, animated menus, scroll-to-top buttons, progress bars, loading spinners, tooltip popups, modal animations -- all of these change CSS dynamically with JavaScript. This is the skill that turns a static page into a polished, professional web app.
2. Performance Matters
Changing styles the wrong way causes the browser to recalculate layouts, which makes pages sluggish. Knowing the difference between toggling classes (fast) and setting inline styles in a loop (slow) makes you a better developer.
3. CSS Variables + JS = Powerful Theming
CSS custom properties (variables) let you define colors, sizes, and spacings once and change them everywhere by updating a single variable from JavaScript. This is how theme switchers work -- change one variable and the entire site updates.
4. It Is the Foundation for Animations
CSS transitions and animations are triggered by class changes. JavaScript adds or removes a class, and CSS handles the smooth animation. This separation of concerns (JS handles logic, CSS handles visuals) is a professional best practice.
Detailed Explanation
Detailed Explanation
1. element.style (Inline Styles)
Sets styles directly on the element's style attribute. Useful for dynamic values that cannot be predefined in CSS:
const box = document.getElementById("box");
// Set styles (CSS uses kebab-case, JS uses camelCase)
box.style.width = "200px";
box.style.height = "200px";
box.style.backgroundColor = "#a855f7"; // background-color
box.style.borderRadius = "12px"; // border-radius
box.style.boxShadow = "0 4px 6px rgba(0,0,0,0.1)"; // box-shadow
// Read a style (only reads inline styles, not stylesheet styles)
console.log(box.style.width); // "200px"
console.log(box.style.backgroundColor); // "#a855f7"
// Remove an inline style
box.style.backgroundColor = ""; // Removes the inline style
// Set multiple styles at once
box.style.cssText = "width:100px; height:100px; background:red;";
// Warning: cssText REPLACES all existing inline styles2. element.classList (CSS Classes)
The preferred way to manage styles. Define styles in CSS, toggle classes in JS:
<style>
.hidden { display: none; }
.visible { display: block; }
.highlight { background-color: yellow; font-weight: bold; }
.fade-in { opacity: 1; transition: opacity 0.5s; }
.fade-out { opacity: 0; transition: opacity 0.5s; }
</style>
<script>
const el = document.getElementById("element");
el.classList.add("highlight"); // Add a class
el.classList.remove("highlight"); // Remove a class
el.classList.toggle("hidden"); // Add if missing, remove if present
el.classList.contains("highlight"); // Check: returns true/false
el.classList.replace("old", "new"); // Replace one class with another
// Why classList is better than inline styles:
// 1. Styles are defined in CSS (separation of concerns)
// 2. Easier to manage (toggle one class vs set 10 properties)
// 3. CSS handles animations/transitions automatically
// 4. Easier to debug (inspect classes in DevTools)
</script>3. getComputedStyle()
element.style only reads inline styles. getComputedStyle() reads the actual, final computed value from all sources (stylesheets, inherited styles, defaults):
<style>
#box { width: 200px; background-color: blue; padding: 20px; }
</style>
<div id="box">Hello</div>
<script>
const box = document.getElementById("box");
// element.style reads ONLY inline styles
console.log(box.style.width); // "" (empty -- not inline)
console.log(box.style.backgroundColor); // "" (empty -- not inline)
// getComputedStyle reads the ACTUAL applied styles
const computed = getComputedStyle(box);
console.log(computed.width); // "200px"
console.log(computed.backgroundColor); // "rgb(0, 0, 255)"
console.log(computed.padding); // "20px"
console.log(computed.fontSize); // "16px" (browser default)
</script>Note: computed values are always in resolved units (px, rgb) even if you defined them in em, %, or hex.
4. CSS Custom Properties (Variables) with JavaScript
CSS variables let you define reusable values. JavaScript can read and change them:
<style>
:root {
--primary: #a855f7;
--bg: #ffffff;
--text: #1a1a2e;
}
body {
background-color: var(--bg);
color: var(--text);
}
.btn {
background-color: var(--primary);
color: white;
}
</style>
<script>
const root = document.documentElement; // The <html> element
// Read a CSS variable
const primary = getComputedStyle(root).getPropertyValue("--primary");
console.log(primary); // "#a855f7"
// Change a CSS variable (updates everywhere it is used!)
root.style.setProperty("--primary", "#06b6d4");
root.style.setProperty("--bg", "#1a1a2e");
root.style.setProperty("--text", "#f1f5f9");
// The entire page updates because all elements reference these variables
</script>This is incredibly powerful. Change one variable and every element using it updates automatically. This is how professional theme switchers work.
5. CSS Transitions Triggered by JavaScript
Define a transition in CSS. JavaScript triggers it by adding/removing a class:
<style>
.box {
width: 100px;
height: 100px;
background: #a855f7;
transition: all 0.5s ease;
}
.box.expanded {
width: 300px;
height: 200px;
background: #06b6d4;
border-radius: 20px;
}
</style>
<div class="box" id="box"></div>
<button id="btn">Toggle</button>
<script>
document.getElementById("btn").addEventListener("click", function() {
document.getElementById("box").classList.toggle("expanded");
});
// The CSS transition handles the smooth animation automatically
</script>6. Reading Scroll Position
window.scrollY gives the vertical scroll position. Use it to add classes based on scroll:
window.addEventListener("scroll", function() {
const nav = document.getElementById("nav");
if (window.scrollY > 100) {
nav.classList.add("scrolled");
} else {
nav.classList.remove("scrolled");
}
});
Code Examples
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--bg: #ffffff;
--text: #1a1a2e;
--card-bg: #f3f4f6;
--border: #e5e7eb;
--primary: #a855f7;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: var(--bg);
color: var(--text);
transition: background-color 0.3s, color 0.3s;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
padding: 20px;
border-radius: 12px;
margin: 16px 0;
transition: background-color 0.3s, border-color 0.3s;
}
.toggle-btn {
padding: 10px 20px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
}
</style>
</head>
<body>
<h1>My Dashboard</h1>
<button class="toggle-btn" id="themeBtn">Switch to Dark Mode</button>
<div class="card">
<h3>Welcome, Aarav!</h3>
<p>You have 5 new assignments and 2 messages.</p>
</div>
<div class="card">
<h3>Upcoming Test</h3>
<p>Mathematics - Chapter 12 - April 15, 2026</p>
</div>
<script>
const root = document.documentElement;
const btn = document.getElementById("themeBtn");
let isDark = false;
const themes = {
light: {
"--bg": "#ffffff",
"--text": "#1a1a2e",
"--card-bg": "#f3f4f6",
"--border": "#e5e7eb"
},
dark: {
"--bg": "#1a1a2e",
"--text": "#f1f5f9",
"--card-bg": "#16213e",
"--border": "#374151"
}
};
btn.addEventListener("click", function() {
isDark = !isDark;
const theme = isDark ? themes.dark : themes.light;
Object.keys(theme).forEach(function(key) {
root.style.setProperty(key, theme[key]);
});
btn.textContent = isDark ? "Switch to Light Mode" : "Switch to Dark Mode";
});
</script>
</body>
</html>:root. All elements reference these variables. When the button is clicked, JavaScript changes the variables using setProperty, and every element updates instantly. The CSS transition makes the color change smooth.<!DOCTYPE html>
<html>
<head>
<style>
:root {
--bg: #ffffff;
--text: #1a1a2e;
--primary: #a855f7;
--accent: #06b6d4;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background: var(--bg);
color: var(--text);
transition: all 0.4s;
}
h1 { color: var(--primary); }
.theme-grid { display: flex; gap: 12px; margin: 16px 0; }
.theme-dot {
width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
border: 3px solid transparent; transition: border-color 0.3s, transform 0.2s;
}
.theme-dot:hover { transform: scale(1.15); }
.theme-dot.selected { border-color: var(--text); }
.demo-card {
padding: 20px; background: var(--primary); color: white;
border-radius: 12px; margin-top: 16px;
}
.demo-btn {
padding: 8px 16px; background: var(--accent); color: white;
border: none; border-radius: 6px; cursor: pointer; margin-top: 10px;
}
</style>
</head>
<body>
<h1>Theme Switcher</h1>
<p>Choose a theme:</p>
<div class="theme-grid" id="themes"></div>
<div class="demo-card">
<h3>Sample Card</h3>
<p>This card uses the primary color as background.</p>
<button class="demo-btn">Action Button</button>
</div>
<script>
const themes = [
{ name: "Purple", bg: "#ffffff", text: "#1a1a2e", primary: "#a855f7", accent: "#06b6d4", dot: "#a855f7" },
{ name: "Ocean", bg: "#f0f9ff", text: "#0c4a6e", primary: "#0369a1", accent: "#06b6d4", dot: "#0369a1" },
{ name: "Forest", bg: "#f0fdf4", text: "#14532d", primary: "#15803d", accent: "#a3e635", dot: "#15803d" },
{ name: "Sunset", bg: "#fff7ed", text: "#7c2d12", primary: "#ea580c", accent: "#facc15", dot: "#ea580c" },
{ name: "Dark", bg: "#1a1a2e", text: "#f1f5f9", primary: "#a855f7", accent: "#06b6d4", dot: "#1a1a2e" },
{ name: "Rose", bg: "#fff1f2", text: "#881337", primary: "#e11d48", accent: "#fb7185", dot: "#e11d48" }
];
const grid = document.getElementById("themes");
const root = document.documentElement;
let dots = [];
themes.forEach(function(theme, index) {
const dot = document.createElement("div");
dot.className = "theme-dot" + (index === 0 ? " selected" : "");
dot.style.backgroundColor = theme.dot;
dot.title = theme.name;
dot.addEventListener("click", function() {
root.style.setProperty("--bg", theme.bg);
root.style.setProperty("--text", theme.text);
root.style.setProperty("--primary", theme.primary);
root.style.setProperty("--accent", theme.accent);
dots.forEach(function(d) { d.classList.remove("selected"); });
dot.classList.add("selected");
});
grid.appendChild(dot);
dots.push(dot);
});
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; }
.nav {
position: fixed; top: 0; left: 0; right: 0;
padding: 20px 30px;
display: flex; justify-content: space-between; align-items: center;
background: transparent;
transition: all 0.3s;
z-index: 100;
}
.nav.scrolled {
background: rgba(255,255,255,0.95);
padding: 10px 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.nav .logo { font-size: 24px; font-weight: bold; color: #a855f7; }
.progress-bar {
position: fixed; top: 0; left: 0; height: 3px;
background: linear-gradient(to right, #a855f7, #06b6d4);
z-index: 200; transition: width 0.1s;
}
.scroll-top {
position: fixed; bottom: 30px; right: 30px;
width: 40px; height: 40px;
background: #a855f7; color: white;
border: none; border-radius: 50%;
font-size: 20px; cursor: pointer;
opacity: 0; transform: translateY(20px);
transition: opacity 0.3s, transform 0.3s;
}
.scroll-top.visible { opacity: 1; transform: translateY(0); }
.content { padding: 100px 30px 30px; }
section { padding: 60px 0; min-height: 50vh; }
h2 { color: #a855f7; margin-bottom: 16px; }
</style>
</head>
<body>
<div class="progress-bar" id="progress"></div>
<nav class="nav" id="nav">
<div class="logo">SiteName</div>
<div>Home | About | Contact</div>
</nav>
<div class="content">
<section><h2>Section 1</h2><p>Scroll down to see the navigation change, progress bar fill, and scroll-to-top button appear.</p></section>
<section><h2>Section 2</h2><p>The navigation shrinks and gets a white background when you scroll past 100px.</p></section>
<section><h2>Section 3</h2><p>The progress bar at the top shows how far you have scrolled through the page.</p></section>
<section><h2>Section 4</h2><p>The scroll-to-top button appears after scrolling 300px down.</p></section>
<section><h2>Section 5</h2><p>All of these effects use CSS classes toggled by JavaScript scroll events.</p></section>
</div>
<button class="scroll-top" id="scrollTop">^</button>
<script>
const nav = document.getElementById("nav");
const progress = document.getElementById("progress");
const scrollTopBtn = document.getElementById("scrollTop");
window.addEventListener("scroll", function() {
// Nav shrink effect
if (window.scrollY > 100) {
nav.classList.add("scrolled");
} else {
nav.classList.remove("scrolled");
}
// Progress bar
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = (window.scrollY / scrollHeight) * 100;
progress.style.width = scrollPercent + "%";
// Scroll to top button
if (window.scrollY > 300) {
scrollTopBtn.classList.add("visible");
} else {
scrollTopBtn.classList.remove("visible");
}
});
scrollTopBtn.addEventListener("click", function() {
window.scrollTo({ top: 0, behavior: "smooth" });
});
</script>
</body>
</html>window.scrollY and document.documentElement.scrollHeight. (3) A scroll-to-top button appears after 300px. All use classList for toggling and element.style for the progress width.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 0; }
.navbar { display: flex; justify-content: space-between; align-items: center; padding: 16px 24px; background: #1a1a2e; color: white; }
.menu-btn { background: none; border: 2px solid white; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 16px; }
.sidebar {
position: fixed; top: 0; right: -300px; width: 300px; height: 100%;
background: #16213e; color: white; padding: 24px;
transition: right 0.4s ease;
z-index: 200;
}
.sidebar.open { right: 0; }
.overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
opacity: 0; visibility: hidden;
transition: opacity 0.4s, visibility 0.4s;
z-index: 150;
}
.overlay.visible { opacity: 1; visibility: visible; }
.sidebar a { display: block; color: white; text-decoration: none; padding: 12px 0; border-bottom: 1px solid #374151; font-size: 18px; }
.sidebar a:hover { color: #a855f7; }
.close-btn { background: none; border: none; color: #9ca3af; font-size: 24px; cursor: pointer; position: absolute; top: 16px; right: 16px; }
.content { padding: 24px; }
</style>
</head>
<body>
<nav class="navbar">
<div style="font-size:20px;font-weight:bold;">MyApp</div>
<button class="menu-btn" id="menuBtn">Menu</button>
</nav>
<div class="overlay" id="overlay"></div>
<div class="sidebar" id="sidebar">
<button class="close-btn" id="closeBtn">x</button>
<h3 style="margin-top:40px;">Navigation</h3>
<a href="#">Home</a>
<a href="#">Courses</a>
<a href="#">About</a>
<a href="#">Contact</a>
</div>
<div class="content">
<h2>Page Content</h2>
<p>Click the Menu button to open the animated sidebar navigation.</p>
</div>
<script>
const menuBtn = document.getElementById("menuBtn");
const closeBtn = document.getElementById("closeBtn");
const sidebar = document.getElementById("sidebar");
const overlay = document.getElementById("overlay");
function openMenu() {
sidebar.classList.add("open");
overlay.classList.add("visible");
}
function closeMenu() {
sidebar.classList.remove("open");
overlay.classList.remove("visible");
}
menuBtn.addEventListener("click", openMenu);
closeBtn.addEventListener("click", closeMenu);
overlay.addEventListener("click", closeMenu);
document.addEventListener("keydown", function(e) {
if (e.key === "Escape") closeMenu();
});
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
#box {
width: 200px;
height: 150px;
background-color: #a855f7;
color: white;
padding: 20px;
border-radius: 12px;
font-size: 18px;
margin-bottom: 16px;
}
#results { padding: 16px; background: #f3f4f6; border-radius: 8px; font-family: monospace; font-size: 14px; }
button { padding: 8px 16px; margin: 4px; cursor: pointer; border: 1px solid #ddd; border-radius: 4px; }
</style>
</head>
<body>
<div id="box">Styled Box</div>
<button id="readBtn">Read Computed Styles</button>
<button id="readInlineBtn">Read Inline Styles</button>
<button id="addInlineBtn">Add Inline Border</button>
<div id="results"></div>
<script>
const box = document.getElementById("box");
const results = document.getElementById("results");
document.getElementById("readBtn").addEventListener("click", function() {
const computed = getComputedStyle(box);
results.innerHTML =
"<strong>getComputedStyle:</strong><br>" +
"width: " + computed.width + "<br>" +
"height: " + computed.height + "<br>" +
"backgroundColor: " + computed.backgroundColor + "<br>" +
"padding: " + computed.padding + "<br>" +
"borderRadius: " + computed.borderRadius + "<br>" +
"fontSize: " + computed.fontSize + "<br>" +
"display: " + computed.display + "<br>" +
"position: " + computed.position;
});
document.getElementById("readInlineBtn").addEventListener("click", function() {
results.innerHTML =
"<strong>element.style (inline only):</strong><br>" +
"width: '" + box.style.width + "'<br>" +
"backgroundColor: '" + box.style.backgroundColor + "'<br>" +
"border: '" + box.style.border + "'<br>" +
"(Most are empty because styles come from the stylesheet, not inline)";
});
document.getElementById("addInlineBtn").addEventListener("click", function() {
box.style.border = "3px solid #f59e0b";
results.innerHTML = "Added inline border. Now element.style.border = '" + box.style.border + "'";
});
</script>
</body>
</html>element.style and getComputedStyle(). element.style only shows inline styles (most are empty because styles come from the stylesheet). getComputedStyle shows the actual computed values from all CSS sources, returned in resolved units (px, rgb).<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.progress-container {
width: 100%; max-width: 400px;
height: 24px; background: #e5e7eb;
border-radius: 12px; overflow: hidden;
margin: 16px 0;
}
.progress-fill {
height: 100%; width: 0%;
background: linear-gradient(to right, #a855f7, #06b6d4);
border-radius: 12px;
transition: width 0.3s ease;
display: flex; align-items: center; justify-content: center;
color: white; font-size: 12px; font-weight: bold;
min-width: 30px;
}
button { padding: 8px 16px; margin: 4px; cursor: pointer; border: 1px solid #ddd; border-radius: 6px; }
.controls { margin-top: 12px; }
</style>
</head>
<body>
<h2>Progress Bar</h2>
<div class="progress-container">
<div class="progress-fill" id="fill">0%</div>
</div>
<div class="controls">
<button id="minus10">-10%</button>
<button id="plus10">+10%</button>
<button id="reset">Reset</button>
<button id="auto">Auto Fill</button>
</div>
<script>
const fill = document.getElementById("fill");
let progress = 0;
function updateBar() {
fill.style.width = progress + "%";
fill.textContent = progress + "%";
}
document.getElementById("plus10").addEventListener("click", function() {
progress = Math.min(100, progress + 10);
updateBar();
});
document.getElementById("minus10").addEventListener("click", function() {
progress = Math.max(0, progress - 10);
updateBar();
});
document.getElementById("reset").addEventListener("click", function() {
progress = 0;
updateBar();
});
document.getElementById("auto").addEventListener("click", function() {
progress = 0;
updateBar();
const interval = setInterval(function() {
progress += 5;
updateBar();
if (progress >= 100) clearInterval(interval);
}, 200);
});
</script>
</body>
</html>fill.style.width. The CSS transition makes width changes animate smoothly. The "Auto Fill" button uses setInterval to incrementally increase the progress. Math.min and Math.max keep the value between 0 and 100.Common Mistakes
Using element.style to Read Stylesheet Styles
// CSS: #box { background-color: blue; }
const box = document.getElementById("box");
console.log(box.style.backgroundColor); // "" (empty!)const box = document.getElementById("box");
const computed = getComputedStyle(box);
console.log(computed.backgroundColor); // "rgb(0, 0, 255)"element.style only reads inline styles (set via the style attribute or JS). Use getComputedStyle(element) to read the actual applied styles from all sources.Forgetting that CSS Variables Need the -- Prefix
root.style.setProperty("primary", "#a855f7"); // Missing --
// Does not set a CSS variableroot.style.setProperty("--primary", "#a855f7"); // Correct--. Without the prefix, it is just a regular (invalid) CSS property that has no effect.Animating Properties That Cannot Be Transitioned
<style>
.box { display: none; transition: display 0.5s; }
.box.visible { display: block; }
</style>
/* The transition does not work -- display is not animatable */<style>
.box { opacity: 0; visibility: hidden; transition: opacity 0.5s, visibility 0.5s; }
.box.visible { opacity: 1; visibility: visible; }
</style>display is not animatable. Use opacity and visibility instead for fade effects. Alternatively, use transform: scale(0) to scale(1) for shrink/grow effects.Setting style.cssText Replaces All Inline Styles
const el = document.getElementById("box");
el.style.color = "red";
el.style.fontSize = "20px";
el.style.cssText = "background: blue;"; // Destroys color and fontSize!// Option 1: Set individual properties
el.style.backgroundColor = "blue";
// Option 2: Include all properties in cssText
el.style.cssText = "color: red; font-size: 20px; background: blue;";cssText replaces the entire inline style. It does not append. Either set individual properties or include all desired properties in the cssText string.Summary
- element.style.property sets inline CSS styles. Use camelCase: background-color becomes backgroundColor, font-size becomes fontSize.
- element.style only reads INLINE styles. Use getComputedStyle(element) to read the actual applied styles from all CSS sources (stylesheets, inherited, defaults).
- classList.add/remove/toggle/contains is the preferred way to manage styles. Define styles in CSS, toggle classes in JS. This keeps style logic in CSS and behavior logic in JS.
- classList.replace('old', 'new') swaps one class for another in a single operation.
- CSS custom properties (variables) are defined with -- prefix on :root. Change them with document.documentElement.style.setProperty('--name', 'value'). All elements using that variable update automatically.
- Read CSS variables with getComputedStyle(root).getPropertyValue('--name').
- CSS transitions handle smooth animations. JavaScript just toggles classes; CSS handles the animation timing, easing, and duration.
- window.scrollY gives the current vertical scroll position. Use the scroll event to add/remove classes based on scroll position (sticky navbars, scroll-to-top buttons, progress bars).
- getComputedStyle returns values in resolved units: px for sizes, rgb() for colors, even if the original CSS used em, %, hex, or named colors.
- style.cssText replaces ALL inline styles. Set individual properties when you want to preserve existing inline styles.