CSS, we all love it right? Truth be told, we owe a lot to CSS. Without it, websites would still look like Word documents. All in all, CSS is an ingrained component of the internet, it’s here to stay, and it’s what makes the internet today. So what’s this article about?
CSS is extremely unpredictable
The problem with CSS, is that there are so many things that just do not work at all how you would expect.
You’re expecting one thing, but no, it happens to work in the exact opposite way, and you would never know it unless you’ve specifically learned about it. It’s just impossible to just figure it out by what else you might know, as it simply doesn’t follow the pattern.
For me in my web development journey, CSS was by far the most difficult and frustrating thing to learn and use.
HTML is very simple and can be learned well quite quickly.
Then there is CSS. The basics of CSS are very simple to learn, sure. Unfortunately you’ll frequently encounter things that just do not work the way you’re expecting. You’ll be unable to overcome them, you won’t know of any solutions, and they will frustrate you.
Even if you find a solution on something like Stack Overflow, you’ll still have no idea how it works unless you actually research further. Things are definitely not predictable with CSS.
So the aim of this article is to introduce you to the top 5 CSS gotchas that will otherwise repeatedly frustrate you throughout your career. It also acts as a good checklist of things that you really should learn to really master CSS.
If you’ve done CSS for any significant amount of time, you’ll definitely have encountered this concept. Margin-collapsing concerns margins that are touching / are next to each other / are colliding… but only vertically. What happens is that instead of being added together, the margins combine into a single margin. The resulting margin is the largest of the two margins, not the combination of both margins added together. For example, notice the pen below:
The red box has a
50px. The blue box has a
You would expect the margin between them to be the sum of the two,
80px in total. But instead, the total margin between them is
This is a behaviour known as margin-collapsing. The two margins collapse into each other, leaving only the largest of the two margins.
To make things worse, this only happens when margins are touching vertically. It does not happen with side-to-side margins. It also happens when both margins are negative (vertically), but not when one margin is negative and the other is positive.
Margin-collapsing is something you definitely need to know about, as it will be a hurdle in your career if you don’t. Here are some good resources to learn more:
Block formatting contexts (a brief interlude)
Also margin-collapsing only affects things within the same block formatting context.
In a few words a block formatting context is basically a container which fully contains something. As a result, the thing it contains can’t interact with anything outside of the block formatting context. It’s effectively too far away to have any contact with anything outside of it.
There are a few ways to create block formatting contexts, for more information please see the article Block formatting context (MDN).
Containing block, absolute positioning
Almost everything in CSS is contained by the content box. Except for things with absolute positioning. For example, consider the pen below:
The statically positioned block is contained by the content-box of the parent. The absolutely positioned block is contained by the padding-box of the parent. So just remember that all absolutely positioned blocks are contained by the padding-box and everything else is contained by the content-box. For more details, see the article Containing block (MDN).
Containing block, absolute positioning, part 2
What is the containing block? Normally it’s the closest ancestor that has a
position property with a value other than
static. But that’s not the sole condition. The same effect is achieved if an ancestor has:
- A transform property with a value other than none.
- A filter property value with a value other than none.
- A will-change property value with a value other than none.
Consider the pen below:
Notice that the containing block does not have a
position property, instead it has a
transform property. For more details on how this works, see the see the article Containing block (MDN).
Vertical-align: middle; and centering in CSS
When you know how to center things well in CSS is when you can really feel like your skills are coming together with CSS. Thankfully, this is much easier to do today now that we have flexbox. Just note that in all likelihood,
vertical-align: middle; does not do what you think it does. It only works for tables or
inline-block content. You cannot use it to align blocks vertically. To save you a lot of trouble, have a look at the article Centering in CSS: A Complete Guide (CSS-Tricks).
How well do you know flexbox? Flexbox is quite amazing and incredibly useful for every developer out there to know, it can help with almost any layout. The problem is that if you don’t know exactly how flexbox works, it can be pretty dumbfounding figuring out why things are just not working and not sizing exactly as you would expect. Here is a small explanation of the
flex property -
flex-basis is like the
width property. It dictates the
width that the element should have before any shrinking or growing occurs. If
flex-shrink are set to
width of the element will be equal to the
flex-basis. For example:
flex: 0 0 200px; would make the element always have a
200px. It would never shrink or grow if the width of the viewport changed. Try it out in the pen below:
If there is more space than the
flex-basis of the element, then it has the capacity to grow.
flex-grow comes in effect. So if there is free space, it will be divided among elements with a
flex-grow value above
0, proportionally. For example, if you have 3 elements, each with
flex-grow: 1; and
300px of free space after
flex-basis is applied for all elements, then each element will get
100px extra width. Overall, if you understand that flex-grow only comes into effect after
flex-basis is determined, all works as expected. Notice in the example below how the items grow if there is space available, but they will not shrink under
300px wide each (because
flex-shrink is set to
flex-shrink is more tricky. Similarly to how
flex-grow only applies after
flex-basis has been allocated, so does
flex-shrink does not reduce space proportionally, not in all circumstances anyway. Instead,
flex-shrink shrinks space from each element, so all elements reach their min-content width (the width where their content would overflow if they got any smaller), at roughly the same time. For example, consider the pen below:
Notice how if you resize the viewport,
block-1 shrinks much faster than
block-2, even though both their
flex-shrink values are
1. They do not shrink proportionally like with flex-grow, instead items with more room to shrink reduce in size faster, so that all items reach their min-content width somewhat evenly. Of course changing their
flex-shrink values does change the behavior, but just note that things do not shrink exactly proportionally.
Margin in flexbox
Also note that using
margin: auto; in flexbox perfectly centers an item. More than that,
margin-bottom work just like
margin-right work everywhere else in CSS. In comparison, in the normal box model,
margin: auto; becomes 0 for the top and bottom. Consider the pen below:
Notice how just by using
margin: auto; on the block, it is perfectly centered in the flexbox. For more information on flexbox in general, please see the guides on MDN. The first part is Basic concepts of flexbox (MDN), then look at the sidebar for the rest.
Here are some more worthwhile mentions.
Borders don’t accept percentage values
As the heading says, you can’t use percentages with borders. This applies to more than just borders too, for example box shadows. It can be confusing because you can use percentages for content size (width and height), padding and margins, but not borders. Just keep it in mind.
Responsive iframes (and other embedded content) that retain their aspect ratio
Unfortunately this requires a hack to accomplish with CSS right now. To find out how to do it, see Aspect ratio boxes (CSS-Tricks).
z-index can be complicated if you don’t fully know how it works. The first thing you should know is the default stacking order for elements.
- Non-positioned blocks (statically positioned).
- Floating blocks.
- Positioned blocks (anything other than statically positioned).
Knowing the above,
z-index can help us further control where an individual element appears. Here’s how it works:
Firstly, note that
z-index only works on positioned elements. If an element is not positioned,
z-index will have no effect.
z-index only works relative to its closest ancestor which forms a stacking context. In other words, an element will never go below its stacking context.
For example, the root element (
html) always forms a stacking context. An element will never stack below the root element, regardless of its
z-index. If another element forms a stacking context, then a child element will never stack behind it.
There are many ways to form stacking contexts, with the most common way probably being giving an element a
relative along with a
Finally, elements with
z-index only compete against each other if they share the same stacking context. This can be quite a handful to understand the first time round. For a much more thorough explanation please see Understanding CSS Z-index (MDN).
Percentages in transforms are not relative to the parent container
With the box model, percentages are given an actual value based on the parent. For example, if a parent is 100px wide, and a direct child has 10% width of the parent, it will be 10px wide. If that child is then relatively positioned with
left: 100%; it would end up to the right of the parent, outside of it. But when using transforms, percentages don’t work the same way. With transforms, percentages are relative to the element itself, not its parent. Consider the pen below:
Notice that the element with
transform: translateX(100%); does not end up outside of its parent. That’s because the
100% in this case is relative to the element itself. It only moves to the right 100% of its width, not 100% of its parent’s width. In contrast, the relatively positioned element works as you would expect and ends up outside the parent element. For more information on transforms, please see the guide on using CSS transforms (MDN)
This is a very minor point and a fairly basic concept, but I thought I’d mention it here for the beginners among us.
In short, some CSS properties are inherited and some are not. Inherited properties include things like
color. This effectively means that you can set
font-family on the
body element and all elements in the page will inherit it and have the same
Most other properties are not inherited, such as
padding. They have to be set individually on elements.
It’s worth understanding this concept so that you know whether you have to explicitly write a declaration or you can just count on it being inherited. There are also special keywords that allow you to explicitly inherit properties that otherwise are not inherited, and also to prevent inheritance and such. For more information, please see Cascade and inheritance (MDN).
Where to go from here
CSS is a tricky beast. On the one hand it is quite easy to learn and use, and on the other hand it has many unexpected cases and surprises.
This article mentioned the most significant and most common ones, the ones you’re most likely to repeatedly encounter throughout your career.
When you’re learning CSS, I recommend using these as a checklist to gauge how much of it you really know. To be the best professional you can be, you should ideally know about all of these cases.
Sidenote: If you know why some of these cases work in this way, especially some of the more unexpected ones like margin-collapsing and flex-shrink, then please let us know in the comments! Good luck and study well!