CSS

CSS is the language used to describe how HTML elements should be displayed on screen - colours, fonts, spacing, layout, animations, responsive behaviour. The acronym stands for Cascading Style Sheets. Pairs with HTML (which provides structure) and JavaScript (which provides interactivity) as the third pillar of front-end web development.

The “cascading” part is the trickiest concept. Multiple CSS rules can apply to the same element, and CSS has a specific set of rules for which one wins. Most CSS bugs that aren’t typos are cascade-resolution issues.

What CSS actually controls

Pretty much everything visible. Specifically:

Typography. Font family, size, weight, line height, letter spacing, text colour, alignment.

Colour and background. Element colours, backgrounds (solid, gradient, image), opacity, blend modes.

Layout. Where elements sit on the page. Modern CSS uses flexbox and grid for layout - older code uses floats and positioning, with predictable pain.

Spacing. Margins (space outside an element), padding (space inside), gaps (space between flex/grid children).

Responsive behaviour. How the design adapts to different screen sizes via media queries - the difference between a desktop layout and what shows on phone.

Animation and transitions. Element movement, hover effects, loading spinners, smooth state changes.

Where CSS gets messy at scale

Three failure patterns:

Specificity wars. Two CSS rules conflict. The more specific selector wins. Developers respond by writing more specific selectors. Over time, every fix requires escalating specificity until the codebase has rules like div.container .panel ul li a.button:hover just to override something simpler. Refactoring becomes nearly impossible.

!important everywhere. The “I give up on the cascade” override. Every additional !important makes the next override harder. A codebase peppered with them is a codebase nobody can change without breaking something else.

Dead CSS accumulating. Pages get redesigned, components get retired, but the CSS rules that styled them stay in the file. A typical 5-year-old codebase has 40-70% dead CSS. Nobody dares delete because of unknown dependencies.

How modern CSS practice avoids the worst of this

Component-scoped CSS (Tailwind utilities, CSS Modules, styled-components, Vue/Svelte single-file components) puts the styles next to the component they apply to. When the component goes, the styles go. Specificity wars largely disappear because nothing’s competing globally.

Tailwind is the most opinionated take - write utility classes in the markup, no separate stylesheet to maintain. Polarising but verifiably reduces the dead-CSS problem because there’s nothing to leave behind.

An example

A solo developer launching a niche affiliate site initially wrote custom CSS for every page - about 1,400 lines after 30 articles. Page weights averaged 180KB CSS-only. Site felt slow and adding new article templates took half a day each because the CSS for similar layouts had to be retro-fitted.

The rewrite used Tailwind plus a 50-line CSS file for typography defaults. Total CSS shipped per page dropped to about 18KB after purge. New article templates took 20 minutes to build because layout was just utility classes in the HTML. Page Speed scores lifted from 71 to 94 on mobile, partly from the smaller CSS payload.

The interesting bit: the developer wasn’t a CSS expert. The rewrite worked because the system did the heavy lifting that custom CSS expertise would have done by hand.

Related terms