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:

  • Evaluation and Quick Fixes
  • Reduction and Replication
  • Causes and Fixes

We will look at each stage and then work through a quick example CSS problem.

Evaluation and quick fixes

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:

  • funny white-space around an image? Set to display: block (an image is inline by default so respects white-space).
  • element not flowing correctly? You probably have a float set somewhere.
  • absolutely positioned element not appearing or in the wrong place or sat behind something else? You probably haven’t set the position property of the parent or have created a z-index stacking context with a transform or opacity.
  • pseudo element not displaying? You probably forgot to add any value in content

There 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.

Reduction and replication

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.

Having added/removed the CSS gradually there should now be a solid reduction which replicates the issue and helps illuminate the offending area/code.

The markup?

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.

Cause and fix

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?

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.

Debugging mobile platforms

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.

Computed styles

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.

Conclusion

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:

  • Evaluate the bug and administer any obvious quick fixes
  • Reduce the problem to the essential code needed to replicate
  • Illuminate the cause with a combination of tools and bug tracking
  • Fix the issue with more resilient code or well commented browser hacks or forks as needed