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.
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
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:
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:
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.
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.
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:
Or if you like being funky, you can also go like
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?
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.
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.
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.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.
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.
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.
It seems to work great but it actually has a couple of drawbacks like:
- you can’t set back the font-size with
emsince 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 (
left). It accepts four values:
static: default value, offsets have no effect
relative: offsets move the visual layer but not the element itself
absolute: 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).
… 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%?
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:
WHAT IF THE PARENT HAS
MIN-HEIGHT AND NO
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.
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
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
fixed. So the first thing to do is to make sure your element has a position assigned before even thinking of applying
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, like
z-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: 10is 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
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.
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 (
display: inline-blockon siblings (
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 than
padding: 1px(or any value greater than 0); some browsers even support sub-pixel values)
border: 1px solid transparent(or any border)
The following JSFiddle shows these fixes in action:
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.
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.
Anyway, I hope this helped you understanding quite a few things and save you some future problems. Thanks for reading, see you! 😉