From our sponsor: Ready to show your plugin skills? Enter the Penpot Plugins Contest (Nov 15-Dec 15) to win cash prizes!
CSS is a mess. First introduced in ~1995, it was meant to style basic text documents. Not websites. Not applications. Text documents. It has come a long way since then. Probably a bit too long.
A lot of things were not intended in the first place like multi-column layouts, responsive web design and more; this is why it has become a language full of hacks and glitches, like some kind of odd steam machine with a bunch of extensions.
On the bright side, this is what makes CSS fun (or kinda)! And this is also why we have jobs. Because I’m personally convinced that generating efficient, cross-browser and future-proof CSS is not possible and probably won’t be anytime soon.
Anyway, I’m not here to talk about my convictions but about CSS. Today, we will do a round-up of common CSS problems in order to see how to troubleshoot CSS.
Among others, I picked some really common yet annoying issues:
- Float clearing, an old battle
- How to fight inline-block spacing?
- Understanding absolute positioning
- When to set width / height to 100%?
- How not to screw up z-index?
- What is margin collapsing?
Float clearing, an old battle
I think this has to be the most common wat? moment of CSS. It is as old as the hills, thus I’m one hundred percents sure every single person who has ever coded CSS fell into the trap.
Basically, when an element only contains floated elements, it collapses on itself. This is due to the fact that floated elements are kind of pulled out of the flow so the wrapper behaves as if he has no child at all.
There are a number of ways to fix this. Back in the days, we used to add an empty div
with clear: both
at the bottom of the container. Then, we replaced the div
by a hr
tag. Not much better.
Then Nicolas Gallagher came with a new way to clear floats from the parent without touching the markup at all. After a lot of discussions and tests to bring it down to the minimum amount of characters required to make it work, here is the latest version:
.clearfix:after { content: ""; display: table; clear: both; }
Actually I lied, this isn’t exactly the latest version, but it’s definitely the shortest. If you have to support IE 6/7, you might need to add this:
.clearfix { *zoom: 1; } /* The star is here to prevent other browsers from reading and applying this rule. */
What you probably should do is creating a .clearfix
class in your project that you can apply to your elements in the markup by simply adding it as a class. This is the simplest and cleanest way to deal with floats.
How to fight inline-block spacing?
Let’s continue with positioning elements on the same line, this time not by floating them but setting them as inline-blocks. display: inline-block
has long been underused, yet we finally figured out how it works and why it’s cool. Nowadays, more and more front-end developers get rid of floats for inline-blocks when they have the option to.
I think the main point of inline-blocks is we don’t have to deal with float clearing and other issues we can face when floating elements. Basically, setting an element’s display to inline-block turns it into a hybrid animal: half-inline, half-block. They can be sized, they can have margins but their default width depends on the content instead of being full parent width (among other specifications). Thus, they don’t stack vertically but horizontally.
There you say “what the hell is the problem then?”. Problem is since they are half-inline, they are spaced from each other by the width of a blank character. With a default 16px baseline with a regular font, that is 4px. In most cases, it is about 25% of the font-size. Anyway, this can be annoying when layouting elements. Let’s say you have a 600px wide parent with three 200px wide inline-block children. If you don’t fight this 4px space, they won’t fit into one line (200 * 3 + 4 * 2 = 608).
Thankfully, there are a couple of ways to get rid of these annoying spaces, each of them with their strengths and weaknesses. To be totally honest, there is no perfect solution yet. Let’s look at them, one by one!
Markup-side: removing the spaces
Please consider the following markup structure, corresponding to the test case I described a couple of lines before.
<div class="parent"> <!-- 600px --> <div class="child">I'm a child!</div> <!-- inline-block 200px --> <div class="child">I'm a child!</div> <!-- inline-block 200px --> <div class="child">I'm a child!</div> <!-- inline-block 200px --> </div>
As I said earlier, this won’t fit into a single line because there is one or more space-characters between each element (in our case a line-break and 2 spaces). The first way to fix the problem is to simply remove the spaces.
<div class="parent"> <div class="child">I'm a child!</div><div class="child">I'm a child!</div><div class="child">I'm a child!</div> </div>
This definitely works but it makes the markup very hard to read. Perhaps we can reorganize our tags instead of putting them all on the same line so they remain readable:
<div class="parent"> <div class="child"> I'm a child!</div><div class="child"> I'm a child!</div><div class="child"> I'm a child!</div> </div>
Or if you like being funky, you can also go like
<div class="parent"> <div class="child">I'm a child!</div ><div class="child">I'm a child!</div ><div class="child">I'm a child!</div> </div>
That’s right, it totally works! However I wouldn’t recommend this since it’s kind of counter-intuitive. We’ve been taught to avoid useless spaces inside our HTML tags and even if it isn’t a problem in itself, it makes code ugly. Let’s try something else.
Markup-side: commenting the spaces
What about commenting the spaces instead of removing them?
<div class="parent"> <!-- 600px --> <div class="child">I'm a child!</div><!-- --><div class="child">I'm a child!</div><!-- --><div class="child">I'm a child!</div> </div>
Hey, it’s better! The code is readable and it works fine. Even if it seems odd at first, you would probably get used to something like this. I personally use this method when I have to remove the gap between inline-block elements.
However, some would say this isn’t perfect since it’s a markup-side solution and this kind of layout issue should be a matter of CSS and CSS only. True. This leads us to CSS-side solutions.
CSS-side: letter-spacing
The letter-spacing
property is used to define the space between letters. The idea behind this trick is to reduce the space between letters to “override the blanks”, then resetting the letter-spacing on children so the text looks normal.
.parent { letter-spacing: -0.3em; } .child { letter-spacing: normal; }
This technique is used by Griddle, a Sass-based grid system from Nicolas Gallagher so you can tell this is a pretty serious fix. However I don’t really like the fact that we’re relying on a magic number. Plus with some fonts, you might have to go slightly lower than -0.3em
like -0.31em
or -0.32em
. Adjust to your case.
CSS-side: negative margin
One other way which is pretty similar to the previous one is the use of a negative margin. The main problem is that this fails in Internet Explorer 6 and Internet Explorer 7 which do not like negative margins. Plus, we have to remove the left margin of the first element to make our children perfectly fit into the container.
.child { margin-left: -0.25em; } .child:first-of-type { margin-left: 0; }
If you don’t have to support Internet Explorer 6 and 7 or have a conditional style sheet for these browsers, I think this is a pretty safe solution. I’d probably go with this.
CSS-side: font-size
Last but not least, you can try by setting the font-size of the parent to 0 to make the blank characters 0px wide, then restore it on the children.
.parent { font-size: 0; } .child { font-size: 16px; }
It seems to work great but it actually has a couple of drawbacks like:
- you can’t set back the font-size with
em
since the parent has a 0px font-size, - spaces are not removed in pre-Jellybean Android Stock Browser (as shown by Matt Stow),
- used along
@font-face
, text can lose antialising in Safari 5 (as shown by Doug Stewart), - some browsers don’t allow a 0px font-size, like Chinese Chrome which automatically sets it back to 12px.
So, definitely not the best solution. As I said earlier, I would probably go with the comment way. If you feel like this is all too complicated, you may get back to floating your elements, or better using flexbox.
Understanding absolute positioning
Positioning is tricky and has always been. Most beginners struggle when positioning elements on a page. They often (mis)use the position
property. This property defines how an element is able to be moved with offsets (top
, right
, bottom
and left
). It accepts four values:
static
: default value, offsets have no effectrelative
: offsets move the visual layer but not the element itselfabsolute
: offsets move the element in its context (first, not static ancestor)fixed
: offsets position the element in the viewport, no matter what their position in the DOM is
The real problem occurs when using position: absolute
. You’ve probably already encountered it: you define an element in absolute mode because you want it to be in the top right corner of its parent (like a little close button for a modal or something).
.element { position: absolute; top: 0; left: 0; }
… and it’s in the top left corner of the window. And you’re like “what the hell?”. Actually, this is the intended behavior (not by you, but definitely by the browser). The keyword here is context.
The above code basically tells “I want my element top to be positioned in the top left corner according to its context”. So what is the context? It is the first not static ancestor. It can be the direct parent. Or the parent of the parent. Or the parent of the parent of the parent. As long as it is the first which is not static.
This is a tricky concept to comprehend especially for a beginner, but once you get this you can do pretty much whatever you want with absolute positioning without crying out loud because everything is a mess.
Here is a quick demo to illustrate what we just saw. Two parents, each one with a child absolutely positioned with top: 0
and right: 0
. On the left side, the parent has position: relative
(correct behavior). On the right side the parent is static (fail).
When to set width / height to 100%?
Height: 100%
Let’s deal with the less tricky one first. When to use height: 100%
? Actually the question many of us asked at least once is “how the *bleep* am I supposed to make my page at least the height of the screen?”. Right? Right?
To answer this question, it is important to understand what height: 100%
means: the full height of the parent element. It doesn’t magically mean “the height of the window”. So if you want your main container to have the height of the window, setting height: 100%
isn’t enough.
Why? Because the parent of your container (body
) has a default height of auto
, which means it is sized according to its content. Then, you can try adding height: 100%
to the body element to see… it is still not enough.
Why? Because the parent of body (html
) has a default height of auto
, which means it is sized according to its content (you get the idea). Now what if you try to add height: 100%
to the html element? It works!
Why? Actually, the root element (still html
) is not really the uppermost containing block of your page; there is the “viewport”. To put it simple, it is the browser window. So if you set height: 100%
to the html element, you ask for it to be as tall as the browser window. As simple as that.
To sum up our story with a tiny bit of code:
html, body, .container { height: 100%; }
Done. In case you’re interested in having a more in-depth explanation about the viewport, I highly recommend you this article from PPK.
What if the parent has min-height
and no height
?
Roger Johansson recently discovered that there was an issue with height: 100%
when the parent element doesn’t have a height assigned but a min-height. I won’t go too deep into the article but summarized, you may need to add a height of 1px to the parent element so the child can expand all the way to the min-height.
.parent { min-height: 300px; height: 1px; /* Required to make the child 100% of the min-height */ } .child { height: 100%; }
More on the topic can be found in Roger Johansson’s article.
Width: 100%
Now, let’s deal with width: 100%
. First of all, a quick reminder: as for the height property, setting width: 100%
is the same as asking your element to be as wide as its parent. No surprise here.
Now let me tell you a little secret. width is a very inadequate name for this. The width
property doesn’t specify the total width of an element but its content width, which is completely different.
When adding padding and borders to your width: 100%
element, it no longer fills the width of its parent. No, it overflows. Because of padding and borders. And because width
should have been called content-width. Please consider the following demo to see what I mean.
The parent has a width of 25em
. The child element has a width of 100%
(of its parent’s width) but it also has a padding of 1em (1em left, 1em right so 2em horizontally) and a border of 0.5em (0.5em left, 0.5em right so 1em horizontally) which pushes its width to 25em (100%) + 2em + 1em = 28em. Eeerrr, Houston we have a problem.
There are four ways to fix this. The first one and definitely the best one is to avoid setting width: 100%
especially since it is useless in such a case. The child element is a block level element which automatically expands to the width of its parent (without the issue seen above). Unfortunately, if we are dealing with an inline-block child, we can’t simply do that which leads us to the next fix.
We could avoid using width: 100%
and use required width instead. In our case, 25 – (2 + 1) = 22em. Needless to say this solution sucks since we have to compute the width manually. We need a better way!
The third one would be to use calc()
to do the calculation automagically: width: calc(100% - 3em)
. It still sucks. First, we still have to compute the result of horizontal padding + vertical borders. Secondly, calc()
doesn’t have the best support in the world (no IE 8, no Safari 5, no Opera 12, no Android Stock browser).
The fourth idea is to use box-sizing: border-box
. Basically, it changes the box model so the width property is actually set to the total width of the element, borders and padding included. The best news is the browser support is very good (everything except IE 7- and Opera 9-). And for unsupported browser, we can still use a polyfill.
Long story short: don’t use width: 100%
unless you’re using a border-box
box-model.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
How not to screw up z-index?
All boxes in a page are positioned in a 3 dimensional space: in addition to their vertical and horizontal positioning, elements lie along a Z axis. At first, this seems very simple: elements with a higher z-index appear on top of elements with a lower z-index.
Unfortunately, things are more complicated than that. ‘Cause you see, z-index is totally screwed-up. I’m pretty sure this is the most tricky CSS property in the whole history of CSS. As I’m sure z-index issues are the most frequent and annoying issues one can face when doing some CSS. Hopefully, we’ll try to enlighten the path to solutions.
First things first. The z-index
property has no effect on a static element. In order to be able to move an element on the Z-axis, you have to define it either relative
, absolute
or fixed
. So the first thing to do is to make sure your element has a position assigned before even thinking of applying z-index
.
Now, the thing to know about z-index is that all elements in the DOM are not placed on the same layer. It means z-indexing an element to a very high value may not be enough to make it appear on the foreground. This is called stacking contexts.
To put it very simple, a stacking context is a kind of group based on a single HTML element within which all child elements share the same stacking order (thus the same Z axis). Changing the Z value of those elements may make them overlap each other in the way you want. In the same stacking context, here is how elements are displayed back-to-front (from the CSS specifications):
- the background and borders of the element forming the stacking context
- the child stacking contexts with negative stack levels (most negative first)
- the in-flow, non-inline-level, non-positioned descendants
- the non-positioned floats
- the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks
- the child stacking contexts with stack level 0 and the positioned descendants with stack level 0
- the child stacking contexts with positive stack levels (least positive first)
When things get ugly
Okay, these were the very basics for the z-index property. Knowing this stuff will save you a lot of trouble, that’s for sure. Unfortunately, this isn’t enough. This would have been far too easy!
The thing is, every stacking context has its own Z scale. Basically, an element A in a stacking context 1 and an element B in a stacking context 2 cannot interact through Z-indexes. That means if element A is part of a stacking context at the bottom of the stacking order, there is no way to get it to appear in front of element B in a different stacking context which is higher in the stacking order, even with a very high z-index value.
I feel like we could make a keyword for
z-index: 99999999
, likez-index: face-punch
.— myself after seeing a lot of ridiculous z-index values.
But wait, there is even worse. The html
element forms the root stacking context. Then, every positioned (not-static) box with a z-index value other than auto
creates a new stacking context. Nothing new. Now this is where things get ugly: some completely unrelated CSS properties create new stacking contexts. Like opacity
. And you’re like…
That’s right, the opacity
property creates a new stacking context. So do the transform
and the perspective
properties. This makes absolutely no sense, right? Basically, if you have a totally random element with either a lower than 1 opacity or a different from none
transform, you are on your way to potential issues.
Unfortunately, every z-index issue has its own context (no pun intended) making it very hard to provide a bulletproof advice for all these case. In the end, we can draw a few conclusions:
- Always make sure your element has a position defined before applying z-index
- Stop the 5+ digits z-index, it is absolutely pointless; in most cases something like
z-index: 10
is more than enough - Make sure the elements you want to reorder are part of the same stacking context
- If you still have an issue, make sure you don’t have a transformed or opacified element along the way
On topic, I highly recommend you to read What No One Told You About Z-index by Philip Walton and the official CSS specifications from the W3C. A lot to learn.
Fighting margin collapsing?
I think this is one of the CSS glitches which took me the most time to wrap my head around. I guess you can tell this is as twisted as z-index in a sense. Anyway, margin-collapsing is when top and bottom margins of two elements collapse into the greatest margin of both. For those of you who have a mathematical brain, vertical margin between two blocks is margin = max(block1.marginBottom, block2.marginTop)
.
Fortunately, this is usually the desired behavior. This is probably why it works like that (as intended and described in the CSS specifications). However, sometimes you don’t want a vertical margin to collapse. To understand how to fix this, we first have to see why this happens. Margin collapse can happen in three different cases.
Adjacent siblings
When two adjacent siblings have vertical margins, they collapse into the greatest margin between the bottom one of the first element and the top one of the second element. This may be prevented in a couple of ways:
clear: left; float: left;
on siblings (right
works too)display: inline-block
on siblings (inline-table
works too)
The following JSFiddle shows these fixes in action.
Parent and first/last child
Usually, the parent’s top-margin and the first child’s top-margin collapse into the greatest margin of both. Similarly, the parent’s bottom-margin collapse with the last child’s bottom margin. This phenomenon is also known as “ancestor’s collapsing”. There are multiple solutions to fight this behavior. Most of them consist of adding one of the following properties to the parent element:
overflow: hidden
(or anything else thanauto
)padding: 1px
(or any value greater than 0); some browsers even support sub-pixel values)border: 1px solid transparent
(or any border)display: inline-block
(inline-table
works too)float: left
(right
works too)
The following JSFiddle shows these fixes in action:
Empty blocks
When an empty block has no border, nor padding, nor height, its top and bottom margins collapse into a single one. Then, it can also match one of the two cases above, collapsing once again with the margin of a parent/sibling. However, empty elements are a bad idea in general, so I guess this isn’t a case you’ll encounter very often.
Final words
Waw, that was pretty dense, wasn’t it? And unfortunately this is only the top of the iceberg in matter of bugs, hacks and glitches. Those are the most common problems one can face when doing some CSS, but there are so many more than this, like browser inconsistencies, vendor prefixes mess, selector specificity, cascade and inheritance, and many more.
I recommend you these websites and articles on the topic:
- What No One Told You About Z-index by Philip Walton
- Height in percent when parent has min-height and no height by Roger Johansson’s
- Official CSS Specifications from the W3C
- Micro clear-fix hack by Nicolas Gallagher
- Display inline-block, why it rocks and why it sucks by Robert Nyman
- Browserhacks by Tim Pietrusky and me
Anyway, I hope this helped you understanding quite a few things and save you some future problems. Thanks for reading, see you! 😉
Great article! Your explanation about absolute position really opened my eyes.
Excellent article!=)
nice artical 🙂
Great run through for those getting to grips with CSS.
Great post! As i was reading it, my own problems came right before me. Faced most of them in my long career as a CSS designer. I am now more than convinced being a good graphics designer is all about being proficient with css.
Thanks for all the information but I am still a little confused with the positioning system. Can you please put more examples in there.
You can also use “overflow: auto” to fix the problem with the floated elements.
Amazing post.
What a great article!! I feel SOOOOO much better about my skills now. Just about every problem that is described here, I have had to deal with. I have wanted to scream and have worked hours trying to fix some of these issues. Thank you for showing me that these issues are very common and that maybe I am not the world’s worst web designer.
Amazing Article Hugo. I am one of those beginners who struggle with these types of issues everyday at work. And sometimes (well, okay most of the time, I say to myself “WTF!” am I doing, will I ever learn CSS?”) So, it is good to hear and see others struggling with this besides Myself
I love the related articles at the bottom of this post as well. Keep up the amazing work you all do at Codrops. You all are my “Go To” site for inspiration, help, and just everyday work related issues that I run into everyday. And I love the tutorials as well.
Codrops for LIFE
Great article CSS is the bane of many designer/developer life!
In this fix for margin collapsing you make a mistake:
overflow: hidden (or anything else than auto)
work with hidden, auto and scroll
don’t work with visible
That method of clearing floats is still too much. You don’t need a clearing class at all (which still gets into the markup); setting the overflow property on the parent (containing) element is all that’s necessary.
Thanks for the article Hugo! I was surprised i’d never tried out the box-model method. I love how it works. I love how this works like we all want it to work. There’s only one big concern for me, and that’s the IE support. Personally I’m not a big fan of the .htc files so to go with the CSS solutions provided we still end up defining widths minus paddings for the .no-boxsizing class. […] Between writing this last dot and the square brackets I came to the conclusion I might be willing to go with the .htc file for this… darn.
excelent, a summary of my life
Good read. Really the best rule of thumb is to avoid issues like this before you ever take a design into full development. Really take time to look at the design and spot potential future issues. You wont catch them all, but you can at least limit damage this way. Sucks these exist, but they have been there forever! You really should avoid them; because these seem to be the first thing that will break when a new version of IE comes out ect.
Great article! The CSS mug is very true to life as well!
I love css,but sometimes makes me scream 😀
Awesome! many thanks.
That was complete package of solutions on the css. Great article, i had to read it for 3-4 times to save it in my head. I have book marked it now.. thanks for the article.
You should have pointed out at
z-index
that setting a negative value (like -1) will screw the whole thing up. Setting a negative value anywhere in the CSS forz-index
will make it stop working for the entire site, so be careful with that. No negative values!Great article! The CSS mug is very true to life as well!
Good article, I always have trouble with CSS.
great artcle…….thanks a lottt
Thanks you so much for this article
Excellent article. Over the years I’ve encountered and solved all of these issues many times (and wasted who knows how much time). Seeing the solutions alongside their root cause has been a great help.
yo! big post, thanx!
font-size: 0px <= Opera & IE8 does not support
Great article with lots of very useful information. Thank you
Good article, thank you for sharing.
I fucking love you! In three seconds, you were able to help me fix a major bug affecting the quality of my CSS Code: achieve 100% width with display: inline-block elements.