I’ve done a lot of CSS debugging. Others code and my own. Mobile platforms and standard desktop browsers. Everything from old versions of Internet Explorer to the latest WebKit nightlies. What became apparent to me as I’ve worked with others, is that many people don’t have a set procedure for debugging CSS.
I found, more often than not, I could reduce the time spent on bugs if a methodical problem solving approach was used.
What follows is my own approach.
I’m not attempting to tell you this is the way to debug CSS. It is however very effective for me. If CSS is not the primary language you write, debugging CSS may currently feel like a dark art; following this guidance may help you isolate and deal with CSS bugs more effectively.
Broadly speaking, I can split the debug process into three stages:
We will look at each stage and then work through a quick example CSS problem.
There are many bugs that are simple to fix if CSS is the primary language you work with and/or you therefore have a deep understanding/experience of CSS. If CSS isn’t your primary language then there will be fewer of these quick fixes at your immediate disposal.
These are the kind of common CSS pitfalls that any experienced CSS developer would likely know. Some examples:
contentThere are a heaps of these ‘bugs’. They aren’t actually bugs, merely a developers lack of understanding as to what the browser is doing. More correctly; what your CSS code is telling the browser to do.
Developers that know these CSS idiosyncrasies recognise the problems that result from them instantly and they are therefore ‘bugs’ that are quick for them to fix. Their evaluation of the bug will differ from another developer that doesn’t hold as much CSS knowledge in their head. It’s important to appreciate that the point at which a ‘workflow’ will be needed to work on a CSS bug will be different for different developers.
For a problem that falls outside the ‘Quick Fixes’ that a developer is familiar with, there is likely little value in throwing more properties and values at the Developer Tools hoping for a result. It is possible to get lucky but it is likely that even if the issue is solved, it won’t be easy to determine what and why the issue was fixed.
Instead, when something arises that can’t be easily fixed, ascertain the extent of the problem area and grab the markup (literally copy it from the DOM) and move on to the next stage of debugging: Reduction and Replication.
Pro tip: the DevTools on most browsers will let you select the wrapping element and copy the HTML block. In Chrome DevTools, with the element selected in the DOM, this is ‘Copy > Copy OuterHTML’.
This stage of CSS bug-fixing is far easier with services like Codepen. We basically want to create a reduction of the issue – that means only the code that is contributing to the bug. This allows us to quickly quarantine the bug and narrow our focus down to the root cause.
To be clear, place only the relevant HTML and CSS in the reduction to replicate the issue. You can either type the ‘fresh’ styles in against the markup or paste in the smallest relevant part of your actual CSS code to recreate the issue. If at all possible, refrain from dumping all your existing CSS into the reduction; you want the bare essentials for replication. Adding the CSS gradually in this manner often reveals the problem area all by itself.
As you are getting near instant feedback, you often see the bug exhibit itself as you add a particular property/value.
The inverse approach is, having dumped all the CSS in, remove sections a lump at a time and hope that things reveal themselves. In practice I find this slightly more clumsy but your mileage may vary.
Having added/removed the CSS gradually there should now be a solid reduction which replicates the issue and helps illuminate the offending area/code.
Suppose having created the reduction with minimal CSS, everything
behaves exactly as the original code. This is also useful. We can now
look to the markup.
First thing to do, and do not skip this, is check the validity of the
markup. At the least, even if the validator flags issues we don’t care
about (meta tags for example), we can ensure it is not malformed in some
way. We are hoping to find missing closing tags, unquoted attributes and
basically anything else that might prevent the browser parsing the
content. Use the W3C validator for
this.
Once the validity of the markup has been checked, it’s then helpful to eliminate the possibility of the user agent styles introducing unwanted styles. Here’s how:
Firstly, change all the markup elements to divs (for block type
things) and spans (for inline things) and then ensure elements are being
selected in the CSS by class only. It may also be necessary to change
any overqualified selectors like a.link to simply
.link.
By using un-opinionated markup we are removing any possibility of user agent default styles/behaviour for certain opinionated HTML elements being a problem. Form elements are particularly opinionated in this regard (as we shall see in our example).
Now, having changed all the HTML elements in the markup to just divs and spans, if the reduction appears as intended, the culprit has been exposed: the user agent style sheets are adding unwanted defaults. It’s now possible to go about undoing whatever the user agent is adding by looking to the computed styles panel. More on computed styles shortly.
Suppose at this point, simplyfying the markup has not made a difference. The issue is however reduced and consistently reproducible. It’s now worth testing the reduction in other browsers. Does the same problem persist across Chrome, Internet Explorer, Safari and Firefox? If not, which gets it right? If just one browser does something wrong it’s worth searching the various bug tracking systems:
Is this a known issue with X browser. Or a specific version of X browser? Is a fix on the way? Are there any known workarounds that won’t impact other browsers? At worst could you fork the code to provide the fix for the needed browser?
I’ve detailed filing bugs with browsers a little before and Lea Verou wrote an excellent piece on the process back in 2011.
Another possibility is that a ‘non-destructive’ hack is needed. For
example, I recently encountered a scenario where a box had to be
positioned absolutely to appear visually at the end of an existing box.
Setting left: 100% did exactly what it should in all
browsers except for Internet Explorer (and the mobile equivalents
Windows Phone 8, 8.1 and 10). In this browser there was a gap between
the end of one element and the start of the next. It looked like a
sub-pixel rendering issue so changing the value to
left: 99.99% sorted the problem in Internet Explorer and
crucially didn’t impact other browsers negatively. This is a hack. But
we can reason as to why it works (one browser is rounding up sub-pixels
whilst another isn’t) and so by commenting the CSS accordingly no harm
is done.
Greg Whitworth from Microsoft also pointed me in the direction of some great detail on sub-pixel rounding in browsers. WebKit/Blink rounds 1/64, Gecko 1/60 and Edge 1/100 (via WebKit developer ‘smfr’)
With mobile devices, if you have the device in question physically, rather than merely responding to a user bug report, and the device supports it, nothing beats debugging ‘remotely’ with a cable. Safari with an iPhone connected is excellent in this respect (Mac only), as is Chrome on Android paired with Chrome on the desktop (PC or Mac). For iOS, the Safari dev tools coupled with iOS simulator are also really good if you don’t have the particular device to hand but you will be limited to the more recent iOS versions your particular version of Xcode supports. Having multiple versions of Xcode, for example one that supports iOS6 and iOS7 and another version that supports iOS 8 and 9 is supposed to be possible but it’s never worked well for me.
What if you have older mobile devices to troubleshoot? In those instances, often remote debugging isn’t an option. In this case, it’s wise to get a feel for which developer tools can help you solve which platform rendering issues.
For example, if you are looking at an issue on an old version of Safari or iOS (e.g. iOS 5/6) or the Android stock browser on Android < 4.2, it’s useful to know they both share the same WebKit base. On recent versions of OS X, as mentioned prior, it’s pretty difficult to get a version of the iOS Simulator that gives you an accurate simulation of these devices (the iOS Simulator tends to only support iOS versions a couple back). Instead, the most fruitful approach may be to go and download the last version of Safari for Windows (v5.x if memory serves).
The rendering on that older version of Safari for Windows is actually very similar to those mobile platforms, so the dev tools will likely reveal more there than the latest Chrome/FireFox or the version of Internet Explorer you have. Alternatively, if the OS supports it, go an get an ancient nightly of WebKit.
Similarly, if you are on a Mac and Windows Mobile (8/8.1) is causing you problems, go and download a virtual machine of Internet Explorer 10. Then use the dev tools their as the rendering of IE10 shares a lot with mobile IE 8 and 8.1.
With certain browsers, particular the older mobile browsers, what appears to be a bug may very well be so and therefore finding a workaround will be much easier if you are inspecting the issue on something you can play around with the dev tools on.
One oft-neglected area of the developer tools is the computed styles panel. If you aren’t familiar with computed styles the name is self explanatory – it is the styles that are actually being applied to the element following computation by the browser. This is important because what you have written may not be what is being applied. By the same token, what you have written may not be all that is being applied. Let me give you an example to explain what I mean. Consider this markup:
<fieldset class="outer">
<div class="inner">
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
<label for="" class="item"><span>hello</span></label>
</div>
</fieldset>And this CSS:
.outer {
max-width: 400px;
}
.inner {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
-ms-overflow-style: -ms-autohiding-scrollbar;
-webkit-overflow-scrolling: touch;
white-space: nowrap;
}
.item {
display: inline-block;
width: 100px;
}What width would you expect the outer to be? If you are
thinking 400px, as is written as the max-width in the
styles, I’d forgive you. But that isn’t the width we see. Take a look at
this:
See the Pen mPYqYd by Ben Frain (@benfrain) on CodePen.
What’s going on? Why isn’t the max-width being
respected? I’ll give you a clue. Open the DevTools and take a look at
the Computed Styles panel.
Can you see the culprit?
I’ll put you out of your suspense; by default, a fieldset has a
computed width equal to the width of its content. In Chrome this is
shown in Computed Styles as the newish length value of
min-content on min-width.
The ‘fix’ would be to add a new value to the min-width
property. In this case, min-width: 0 would let our intended
max-width property work as intended.
That’s the value of looking into the Computed Styles section of the DevTools. Remember that what you write may not be what is being computed by the browser.
The reasons for visual anomalies on the web are many and varied. Implementations of specifications differ so browser specific foibles are rife. Besides building up a mental catalogue of ‘gotchas’ the most effective approach to closing down issues is being methodical in dealing with the issues. In summary I have found great efficacy when following this approach: