Treat Layouts as Functions, Not Fixed Paintings
Most of the fragility in CSS comes from a mindset problem. We tend to style a page the way it looks right now, in one viewport, with one set of content. But a layout that works at one width or with one amount of text will often break the moment anything shifts. The fix is to stop thinking about CSS as static rules and start thinking about it the way you'd think about a JavaScript function: something that takes inputs and produces an output. When the input changes, the output should change predictably, not fall apart.
Writing CSS with this lens prevents three common problems:
- Wasted time. Unanticipated side effects mean every change requires hunting down what else it broke.
- Mysterious bugs. A classic example is an online store where the "Buy" buttons disappeared entirely due to a reckless use of viewport units.
- Stagnation. When styles are scary to touch, new features quietly get dropped.
Isolate the Container From Its Contents
In a good JavaScript function, changing one input doesn't accidentally clobber an unrelated output. The same principle should hold for a component. Consider an icon inside a circular badge. If you swap in a narrower arrow icon, the circle gets squished into an oval. That's an unwanted side effect: the inner content is dictating the shape of its outer container.
The fix is to break the dependency between the content and the container's geometry. Two changes accomplish that:
- Give the container fixed dimensions. The circle's width and height no longer depend on the size of whatever sits inside it.
- Decouple size from placement. Use Flexbox to center the icon, so the icon's position doesn't shift when the container resizes.
See the Pen [Arrow Icon Example [forked]](https://codepen.io/smashingmag/pen/OJBpNMv) by Yaphi.
This isn't a universal rule. Some side effects are desirable — add more text to a paragraph and the content below it should shift down. The goal isn't to forbid all interaction between elements. It's to ask which interactions you want to happen and which ones will surprise you later. Too often we copy a published pattern without considering whether its implicit side effects solve problems or create them for our specific case.
Don't Surfocate Your API
When designing a function, you choose which inputs to expose. Think of those as knobs on a TV. Too many knobs make the function painful to call; too few knobs make it impossible to control what you need.
Too Many Knobs
A lightbulb toggle needs exactly one input: on or off. A function that requires you to pass in lamp color temperature, bulb wattage, and a timer schedule for a simple toggle is over-engineered. CSS hits this when you need to make one logical change but have to edit several unrelated properties.
Changing a card's width is a one-liner when the card is the single source of truth:
.card {
max-width: 300px;
}
The alternative — explicitly overriding the max-width on a batch of the card's child components — means making four separate edits to achieve a single resizing. For most components, that's a lot of unnecessary knobs.
Too Few Knobs
Combining too much into one parameter causes equally bad problems. Imagine a function to control a car. It would be "concise" to set speed and turn angle with one value, but such a function is terrifying: stepping on the gas would also steer the wheel. You need them as separate parameters because they're independent inputs.
setCarState({ speedAndTurnAngle: 60 });
CSS hits this when you rely solely on a container's width to control everything inside it. With one max-width on a card, the image inside shrinks whenever the card does. That coupling might be fine for a simple icon badge, but it's wrong for a profile card where the photo should keep its own proportions while the text block wraps.
.card {
max-width: 300px;
}
.photo {
width: max(150px, 50%);
}
The solution is to expose an additional control: a width applied directly to the photo's own class. The rule of thumb is to match your number of "parameters" to what your use case actually requires.
Ask What Happens When Inputs Change
Good functions are resilient; they degrade gracefully when given edge-case inputs. CSS needs the same consideration. Before you ship a style, ask: what happens to the height if the width is constrained? What happens to accessibility if an element slides in from the side? What happens to hover states if the user switches to touch?
Consider a row of three cards inside a container with max-width: 900px. Each card has padding: 5vw. On smaller screens that looks fine, but as the viewport grows past the container's max width, the cards keep expanding while their inner padding keeps scaling with the viewport, crushing the content. The container and its padding have conflicting limits.
See the Pen [Example of padding crushing content [forked]](https://codepen.io/smashingmag/pen/RwepaQQ) by Yaphi.
Three standard fixes:
- Use viewport or container breakpoints to keep padding reasonable.
- Use the
min()function to cap the padding at a set upper bound. - Switch to fixed units like pixels.
All of these start with the same prediction: what happens to this layout as the viewport widens? Building with that question in mind is sometimes called Defensive CSS. The idea is that styles should be "future-proofed" by considering them as transformations from input (viewport, content length) to output (a usable UI).
The Core Question
Structure in CSS works like structure in code: it's a description of how a system behaves under change. Styles written as static constants will always fracture when an input you didn't anticipate comes along.
The next time you write CSS, don't just ask what the layout looks like. Ask how it should respond to change.



