Designing For the Browser: A Practical Guide to CSS

We’ve all experimented with HTML, and we’ve all tried to make our mockup pages look good. Just reading up on it, selecting elements and declaring their style seems simple enough. However, if you’ve worked on a page for long enough, you’ll have most likely found it to be more challenging than it might have initially appeared.

Through a series of desperate steps, your stylesheet and layout – like with any fixer-upper – will become a tangled mess that draws nicely only under certain conditions and screen sizes.

The HTML/CSS pair gives a deceptively simple, yet elegant, method for writing layouts. It seems trivial at first, then things start drawing ‘incorrectly’, then they seem trivial again.

Most of the guides, tutorials, etc. on CSS that I’ve read Given, I haven’t read nearly as much as I could have, and compared to the entire corpus of CSS-related posts online, I barely make a representative sample.

The best resource for learning new concepts, or the concepts behind features like CSS Grid is CSS Tricks. Handholding might not come included though. For quick reference to the spec, MDN is by far the best.
focus on particular goals or aspects, like animations, or naming conventions — instead of the intuition behind it all. They (usually) don’t give newcomers grips over the subject, and are too long and laborious for veterans needing a quick refresher.

This article aims to give a non-exhaustive list of general guidelines that should make your future styling and layouting endeavours more rewarding and enjoyable.

Naming Conventions – and Which One You Must Use #

As I’ve already mentioned, this article is a list of useful tips to keep in mind, primarily things I wish I had known about when I was learning CSS. However, this article is not a general guide to CSS, or design architecture.

While I can (and occassionally will) give suggestions on (personal) best practices, I expect the reader to exercise a healthy dose of reason. I also expect of the reader to have some previous experience with programming in general: the intuition from there will lend itself useful here, as well.

The sixth sense for detecting spaghetti code comes from a decent mileage in writing spaghetti code. As such, I won’t waste your time or insult your intelligence by convincing you of a Best Naming Convention.

However, there are some useful encapsulation patterns you should know about.

Utility Classes #

Usually, we describe what an element is semantically with classes. A button might be .disabled .success, where the rules for those two classes are a selection of styles: they might affect position, :hover, colour, all at once.

This is a great method for describing the style of elements using their semantics. However, it only works if you have the relatively same layout everywhere. Otherwise, you’ll end up needing only some parts of a class, and only in some of your elements.

As tempting as it may be to just split it into two classes, that partitioning will most likely continue creeping on you until you’re left with a bunch of nonsensical, duplicated classes.

This is, more or less, what utility classes aim to solve. Instead of classes being aliases for a certain kind of style, utility classes are aliases for certain kinds of stylings.

What I mean by this is that while a ‘regular’ class might apply a ready-for-production look to an element, a utility class will only apply a single part of that ‘regular’ class style.

For example, the most common utility classes are padding and margin classes like .pa-2 and .my-1. They only apply padding to all sides, or a margin in the y-direction.

By themselves, they hardly fully style an element, but you can mix and match any of them to get a styled element. They don’t have to be single line rules, either. For example, if you need to vertically center elements in a block container, you can abuse ::before, display: inline-block, and vertical-align:

.align-vertical::before {
  content: '';
  display: inline-block;
  height: 100%;
  vertical-align: middle;

.align-vertical * {
  display: inline-block;
  vertical-align: middle;

This approach lets you style most of your elements in HTML. In fact, popular CSS frameworks are usually just a bunch of utility classes. The downside is that you can (and have to) add margins and paddings, etc. inline across your HTML.

Components #

A really good way of thinking about your layout is dividing it up into components, much like in component-based frameworks like React or the new Web Components.

Title Text

Content Card Title

Image Reiciendis sit libero velit in. Id et animi saepe quidem eaque. Iure suscipit est odit necessitatibus impedit ut dolorum. Et asperiores et non eos vel quaerat. Tenetur enim maxime occaecati culpa placeat.

Content Card Title

Image Reiciendis sit libero velit in. Id et animi saepe quidem eaque. Iure suscipit est odit necessitatibus impedit ut dolorum. Et asperiores et non eos vel quaerat. Tenetur enim maxime occaecati culpa placeat.

Above is an example layout for a page. It’s got the content centerpiece, with two (a)side columns. Apologies for the colours, I’m using default CSS colour names.

Nearly each element has a different background colour set, so that you may differentiate them without looking at the markup. If we analyse the content, we’ll see that some things repeat. Namely, the content cards, and aside cards.

Obviously, we want to avoid code repetition, so that we don’t have to style each card anew. Thus, we can make a class .card that will style the entire thing using selectors like .card > img and .card:first-child, etc. Although it’s not according to spec, you could write <card> elements and card selectors, and most browsers will still render it correctly.

This comes at the cost of having a very strict layout you have to follow, if your CSS isn’t very flexible. This means the title has to be top-level, or first child, or whatever.

So instead of having a single class to style your entire element subtree, you have a class to discriminate your component, and then subclasses that style parts of that component.

This is where the naming conventions kick in: you can use class names like .post and then just .title, so you’d be selecting .post > .title, but what if it’s not top-level? Then you’re selecting for .post .title, but what if you have another unrelated .title further down the subtree? Then you should use .post--title (-- being like a private field accessor, since CSS names are usually kebab case)

A Compromise Solution #

I started this paragraph saying I wouldn’t insult your intelligence, yet here we are discussing naming schemes. However, there is a good reason for that!

Component-type styling is great for encapsulating a common style pattern that repeats many times across your HTML, but it’s too rigid for all possible places you might put them.

So instead of bodging your component classes to work, write them as general as possible, and as layout-agnostic as possible, and then use utility classes to position them correctly. For example: <div class="post ma-2 glow-on-hover" />

And if you’re using Sass, Less, Tailwind, or any CSS preprocessor, you can usually use a macro like @extend .class, which will paste that selector’s style into your class. That way, you can write your component classes using utility classes, too.

Packing Up #

You might have heard of CSS having a ‘box model’. Even in the actual spec, everything in the layout will decay into a (rectangular) box. You can see this when you mouse-over an element in devtools.

Thinking of everything as rectangles might seem odd, but it simplifies operating on many things significantly! For example, when rendering, the browser will determine the width, height, and position of the top-most rectangles, and then recurse down into its child boxes. This is also how things like transform work: they operate on the associated box of the element.

Keeping this in mind, we can diagnose every render – and rendering artifact – as an interaction of sibling boxes. Child boxes are rendered in a particular flow and layout, primarily are rendered to fit their parents, and compete with each other for space.

When something is drawn at the incorrect size, try to think which box is affected, why, and by what. Sometimes it’s the parent box that’s offset!

Flow Types #

I mentioned earlier that boxes render their children in different flows and layouts. While I’m not sure of the exact definitions as per WHATWG specification, I used layout to refer to block layouts like grid or flex.

However, flow is a bit more closer to the original use in the spec. Generally, boxes are rendered either as blocks or inline.

Block Flow #

The block flow type is the default for all elements, except some with user-agent stylings like <span>s. Block elements break lines after themselves, causing block siblings to render one under the other.

Block elements will try to take up as much space horizontally as possible, taking up at least as much space as its contents are wide. This means you can center things horizontally with text-align. Centering things vertically, however, is a different problem altogether.

Although it renders vertically by default, flex and grid let you position block elements side-by-side.

Inline Flow #

If you look at the way text flows, you’ll notice that words, when reaching the edge of the containing box, they are placed into a new line and continue rendering there.

This type of rendering which breaks children into lines is exactly what display: inline is for: your elements will hesitate to change the width of the parent, and will instead move down a line.

This is how text nodes render words, sort of Astute Unicode users, foreign-script scribes, as well as typesetters, will notice that text also gets hyphenated, might be context-senstively rendered wrt it’s width, etc. This is why software like Harfbuzz exists.. Text nodes’ effects on the containing box are weird. You can change the line-break behaviour with whitespace.

Inserting Blocks Into Text #

If you’ve ever needed to render an element in the middle of text, respecting its flow, you’ll have most likely seen how a block <div> splits your otherwise single paragraph. These elements need to be displayed as inline, or inline-block.

The difference is that inline-block takes on more properties of a block element. You can set width, height, and top/bottom paddings and margins.

However, this still ‘breaks’ text, in the sense that the words before the element will also render before it, and likewise for the words after it. This means you can’t make words flow around an inline-block element and properly fill out and justify a line.

If you look at the footnote element on this website on narrow screens – and especially if you play with the viewport width, you can see the text flowing around the footnotes. Try inspecting the element, and disabling the float: left.

See how the line right before the footnote is justified, sometimes causing one or two words to be stretched out, even though justify should leave the last line intact? This is because the footnote is inline (nevermind the deeply magical rendering), so the render algorithm thinks the footnote is part of a line, and that it’s all part of one paragraph.

However, the footnote takes an entire line width, breaking into a new line, causing this issue. To let block elements be drawn inside inline flows, while the inline draws around it, you can use float.

In this case, it lets words flow around the footnote so as to properly justify the text. This means that the footnote might render a few lines after where it is in the DOM.

Naked Boxes #

In rare cases, you might need to group elements together in a container, but render them without that containing box.

On this website, I group footnotes and the reference [1] in a <span> so I can make both change colour on :hover. However, on narrow screens, the footnote and reference are rendered separately, inside the surrounding inline flow.

This is only possible if that containing <span> doesn’t create a box that constrains the children. This is what display: contents does. Note that browser support for this is a bit spotty, at least for production standards.

Simple and CSS-Compatible Layouts #

A lot of times, and partly due to the interactive nature of CSS ‘development’, you might argue with your browser about where it’s drawn an element, and where it should have drawn it instead.

While this reasoning isn’t incorrect, element positioning should be something happening automatically and implicitly, without you necessarily having to spell it out.

Proper HTML/CSS layouts feel like writing an idiomatic Makefile, using a lot of implicit rules, automatic variables, etc. leaving a fully working Makefile that look quite enigmatic to the uninformed passer-by.

What I mean by this is, if something is drawn 6px to the left of where you want it, adding a negative 6px It might not seem like it at first, but the CSS px unit isn’t actually a pixel. It used to be, but since people were expecting the common 96ppi screen density in their CSS, px was christened to be 1/96th of an inch. right margin to it is not what you should do. This is the type of spaghetti code that appears in CSS. It’s indicative of bigger underlying problems, and putting a negative margin will just allow those problems to cascade (get it?) further. After a few negative margins, you’ll find that you can’t touch anything else because everything will start to render offset by the random px amount you sprinkled everywhere.

Likewise, using margins to position things in general is bad. And using paddings for this is worse. And using px values for positioning is also bad. There are cases when you will need (or even should) do this, but it should be a conscious choice, not a fix.

So, instead of thinking about where things should be, you should instead think of how things should be drawn. After all, CSS is very declarative, and your thinking should be too.

For example, if you’re writing a blog kind of layout, not unlike the layout I showed earlier, you will first need three columns. This means you have three sibling divs for those columns in a containing parent. Those siblings shouldn’t handle how they’re being rendered, or know about their siblings. Instead, specify in the parent that its children are columns.

Like this, since the children’s column-rendering is controlled by the parent, you can render the columns with flex, grid, (god-forbid) tables, or something more exotic, without touching the children (more-or-less).

Your HTML layout doesn’t need to know how it’s going to get rendered. It just needs to encapsulate data correctly. A great example of this is the CSS Zen Garden. It is the same HTML layout, just with (radically) different stylesheets.

Thinking in Layouts; Or, Why Everything is Flex #

Let’s say you’ve got a layout in mind. Maybe you even drew it on paper. How do you go about transferring it into HTML/CSS?

As I’ve talked about in the last paragraph, your HTML should only contain the boxes for your content. So, your columns are container elements that should be siblings. Then, depending on how you further partition space, you might need more or less containers, containing other containers or lists of items, and those items might be complex components like cards, etc.

The browser will parse your HTML into a tree, with <html/> being the starting node. Until and unless you’ve got the intuition for it, you should draw that tree yourself – manually – for your layout; and then see if it makes sense for each subtree to be a child of its parent. For example: the title for a card shouldn’t be a sibling to the card it’s describing.

Note that you can (sort of) get rid of containers in CSS, but you cannot add them, so adding extra elements (especially around text nodes, so that text nodes have no non-text siblings) will be less of an issue than not writing enough.

Soon you’ll notice that most, if not all of your layout is a set of containers, splitting their parent either vertically, or horizontally, either in a certain proportion or based on their content.

This is exactly what flexbox was made for: partitioning a container along a certain axis.

A Few Tips on Flexbox #

Although a technical understanding of flex isn’t necessary to use it, a little bit of it does help. Most issues you experience with insolent flex containers will be in one of two areas: unexpected line breaks (or lack thereof), and unexpected space partitions (competing elements).

Flexbox starts rendering by putting its children side-by-side, at their basis size (controllable by flex-basis), and then shrinking and expanding them to meet flex-grow and flex-shrink demands.

A non-zero flex-shrink is not the same as flex-basis: 0! Shrinking will not happen beyond the point of where 100% of the item’s space is taken up by content, and squishing it more would mean overflowing the element.

Likewise, flex-shrink defaults to 1, meaning some of your elements may be inclined to shrink even when you don’t want them to.

A Word on Gutters #

Sometimes, you’ll want a fixed size (read: exactly nn px wide) detail in your layout – something like a gutter – in which you can place stuff in, but in line with the elements. Basically, you can’t have a long, narrow, gutter container in which you’d dump all this.

Instead, you have to reserve the same amount of space on the - say - left side of each list element, and render something in there for some list elements.

Usually, you’d be forced to duplicate this px size literal across your styles. However, you can use custom properties, i.e. CSS variables, a la --gutter-size: 8px, and then var(--gutter-size).

Custom properties have the benefit of cascading down elements like normal styles. Using a CSS preprocessor here can help too, especially if you somewhat complex logic on positioning the optional ‘gutter’ element that is duplicated.

Don’t Abuse What You Don’t Understand #

Ideally, no code you write (or copy-paste), worse yet - execute, should be unclear to you. Deadlines and other pressures usually make this stipulation too stringent.

CSS is no exception, and you should err on the side of caution if you’re not too sure of your abilities. It can do wild stuff, like make vertical scroll go horizontally:

item 1

item 2

item 3

item 4

item 5

item 6

item 7

item 8

The more something “abuses” a specific CSS feature, the more you should be aware of what it’s doing, since that usually makes it go into genuinely weird territory.

For example, the above example works by rotating everything twice with a transform, which in turn swaps the width and height properties for some aspects, but not others: width: 100% sets the weird scroll container’s height to be as wide as the parent container, etc.

These edge cases can get a lot more insidious, and without knowing what’s exactly going on, you could introduce a singularity into your layout around which everything else is bent to appease.

Pseudoelement Magic #

There exist only a few pseudo elements in CSS, of which even less are commonly useful. There’s the ::selection for selected text, and ::marker for lists.

But then there’s also the ::before and ::after pseudoelements. These pseudoelements let you, practically, create an element inside the specified container at the front/end of its children.

You can control its content with the aptly named content property, which will set the element’s text content. This means no HTML injections.

Using that content is useful enough; everything that’s numbered on this website is done through CSS counters, and the numbers are displayed using a pseudoelement.

Its usefulness is, perhaps, least found in this aspect. Instead, you can use an empty content and position and resize the pseudoelement, for one reason or another.

For example, you could make a universal .lighten-on-hover utility class that would increase the brightness of the background on hover by stretching a ::before to match the size of the container exactly, placing all other children in front of it on the z-axis, and setting the ::before background to a transparent white on hover.

With some clever use of selectors, you can automatically add the external link pictogram after links. Provided there’s some regularity to your links, you can use attribute selectors to match against href of <a/>.

On this site, all internal links are relative, while all external links start with the usual /https?:\/\//. Hence:

a[href^="http://"]:after {
  content: "";
  display: inline-block;
  width: 12px; height: 12px;
  background-size: contain;
  background-image: url("/external_link.png");

Likewise, a lot of other things on this site don’t exist in HTML. Once you’re comfortable enough with it, you’ll see how pseudoelements become an invaluable tool, capable of both improving layouts and writing witty workarounds.

Read Up On CSS APIs #

A lot of times, you’ll hit the apparent limits of CSS. And while such boundaries do exist, they’re a lot fuzzier than they might first appear.

There is rarely an immediately obvious solution to exotic layout needs, and the solutions might involve disparate features. Half of finding such a solution is a lot of creativity. There’s not much I can do to help with that, at least not in passing in this short section. However, the other half is knowing all the possibilities.

So, to help yourself find these exotic abuses of the spec (or prolong your attempts before giving up), you should skim all the features every once in a while, since you’ll find and understand things you didn’t the last time around.