Where CSS Meets JavaScript
CSS keeps the wardrobe. JavaScript is the stage manager who calls the costume changes.
All through this track, whenever something needed to react, like opening a menu when someone clicks a button or switching to dark mode when someone flips a toggle, I've said "that's where JavaScript comes in." This lesson shows you exactly how the handoff works, so you walk into the JavaScript track already knowing where the two meet.
The wardrobe and the stage manager
Picture a theater production. Backstage there's a wardrobe department. Every costume for every scene has already been designed, sewn, and hung on a labeled rack: "Act 1, ballroom." "Act 2, rainy street." "Act 3, royal wedding."
During the show, the stage manager doesn't sew anything. They don't pick colors or fabrics. They just call out: "Costume change, Act 2!" And the actors swap into outfits that were designed long before.
That's the professional way CSS and JavaScript work together:
- CSS is the wardrobe. It defines every look in advance: what a menu looks like open, what it looks like closed, what dark mode looks like.
- JavaScript is the stage manager. It watches what the visitor does, and when something happens, it calls the change, usually by adding or removing a class.
JavaScript decides when. CSS decides what it looks like. Each does the job it's good at.
Calling the costume change: classList
/* CSS: the costumes, designed in advance */
.menu {
display: none;
}
.menu.is-open {
display: block;
}// JavaScript: the stage manager
const button = document.querySelector('.menu-button');
const menu = document.querySelector('.menu');
button.addEventListener('click', () => {
menu.classList.toggle('is-open');
});classList is how JavaScript works with an element's classes:
classList.add('is-open')puts the class on.classList.remove('is-open')takes it off.classList.toggle('is-open')flips it: on if it's off, off if it's on.classList.contains('is-open')checks whether it's there.
Notice how little the JavaScript knows about appearance. It doesn't know the menu slides in, or what color it is. If a designer later decides the open menu should fade in with a transition from Transitions and Animations, they change the CSS, and the JavaScript doesn't need to change at all.
Many teams name these state classes with an is- or has- prefix (is-open, is-active, has-error) so it's obvious they're switched on and off by JavaScript.
Even better: style the state that's already there
Remember aria-expanded from Accessibility Basics? A menu toggle button should tell screen readers whether the menu is open. So JavaScript has to update that attribute anyway. Why not let CSS read it directly, with an attribute selector from Selectors?
.menu {
display: none;
}
.menu-button[aria-expanded="true"] + .menu {
display: block;
}button.addEventListener('click', () => {
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!isOpen));
});Now there's only one source of truth. The accessible state and the visual state can never get out of sync, because the visual is the accessible state. If the screen reader says "expanded," the menu is open. Always. This is one of those small patterns that separates good front-end work from great front-end work.
Changing a CSS variable from JavaScript
In CSS Variables, you built a theme from custom properties. JavaScript can change them while the page is running:
document.documentElement.style.setProperty('--color-brand', '#b3261e');document.documentElement is the <html> element, the same thing :root targets. One line, and every button, link, and heading that uses var(--color-brand) updates instantly. That's how "pick your accent color" settings on websites work.
A theme switch often combines both techniques: JavaScript sets an attribute, and CSS re-mixes the variables for it:
:root[data-theme="dark"] {
--color-bg: #111827;
--color-text: #e5e7eb;
}document.documentElement.dataset.theme = 'dark';dataset.theme is how JavaScript reads and writes that data-theme attribute. Remember data-* from Attributes Deep Dive? This is the "back of the name badge" in action.
element.style: use it sparingly
box.style.backgroundColor = 'tomato';JavaScript can also set styles directly with element.style. Notice the property name changes from background-color to backgroundColor in JavaScript (hyphens aren't allowed in those names, so the words get joined with a capital letter).
But here's the catch. element.style writes an inline style, and you know from Three Ways to Add CSS what that means: the highest specificity there is, almost impossible for your stylesheet to override. It's the stage manager grabbing a paintbrush and painting on the costume during the show.
So when is it the right tool? When the value is truly dynamic and can't be designed in advance: a progress bar at exactly 63%, an element following the mouse pointer, a position calculated from the window size. For everything that can be a predefined "look," toggle a class instead.
Reading styles and preferences
JavaScript can also ask about styles:
// What is this element's actual, final color after the whole cascade?
const color = getComputedStyle(heading).color;
// Does the visitor prefer reduced motion?
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;getComputedStyle gives you the final result after every rule, inheritance, and override is resolved: the answer to the cascade question. matchMedia lets JavaScript check the same media queries you wrote in Responsive Design and Media Queries, so a script can skip a fancy animation for someone who asked for less motion.
Try it
Click the buttons in the preview. The JavaScript only flips attributes; the CSS decides everything you see.
HTML
CSS
JavaScript
Preview
Try it yourself
- Change the dark theme's
--color-brandin the CSS pane. The JavaScript doesn't change at all, but the dark mode looks different. That's the separation at work. - Make the open menu display its links in a row using
display: flexand agap, instead ofdisplay: block. - In the JavaScript pane, add one line inside the theme click handler that sets
--color-brandtotomatousingdocument.documentElement.style.setProperty. Notice it now overrides both themes? That's inline-style specificity at work.
Check your understanding
Up next
That's everything. You now have the full CSS toolkit, plus a clear picture of how it plugs into JavaScript. There's one thing left: take the page you built in the HTML track and make it yours. Head to Final Project: Style Your Profile Page. After that, the JavaScript track is waiting.
