Debugging CSS - 2020
Debugging CSS - 2020
It's hard to convey what a profound change it brought in developing for the web
when introduced, although its widespread adoption took years of the technology
maturing in browsers, and of advocacy, changing the long established use of tables
for page layout, font elements, and other hacks web developers came up with to
design for the Web.
Since then, CSS has matured in ways its originators and early adopters could barely
imagine and brings developers incredible power. But this power and complexity
come at a cost.
]e Cascade, Speci^city, Inheritance are all powerful features of CSS, but also
cause many of the problems we associate with the language.
Which is why I'm surprised it's taken so long for someone to really address the
signi^cant challenges of debugging CSS. And why I'm excited for Ahmad's new
book, which addresses this important topic in detail.
I really recommend this to any web developer, it's long over due!
Let’s face it: ]e process of debugging CSS is not straightforward, because there
is no direct or clear way to debug a CSS problem. In this book, you will learn
how to sharpen your debugging CSS skills.
Before debugging a CSS error, you need to spot it ^rst. In some cases, you
might receive a report from a colleague that there is a bug to be solved. Finding
a CSS bug can be hard because there is no direct way to do it. Even for an
experienced CSS developer, debugging and ^nding CSS issues can be hard and
confusing.
Because this book is about debugging and ^nding CSS issues, you should be
aware of a bit of the history of how debugging tools for CSS have developed
over the years.
2 Debugging CSS
1. Introduction and Overview
Style Master
You might be surprised to hear that the ^rst CSS debugging tool was released
in 1998 — 22 years ago! Its creators, John Allsopp and Maxine Sherrin, named
it Style Master. As they described it:
]e goal of Style Master was to make working with CSS more eacient, more
productive, and more enjoyable. As you can see, then, debugging CSS has been
a topic of interest to developers for a long time.
Debugging CSS 3
1. Introduction and Overview
In 2006, Joe Hewitt released the ^rst version of the Firebug browser extension.
Without the e`ort of the folks who created these great tools, Style Master and
4 Debugging CSS
1. Introduction and Overview
Firebug, we might not have the DevTools we use today. We couldn’t be more
thankful to them.
]e scene today is dramatically di`erent. Every browser these days has built-
in DevTools that make it easy for a developer to inspect and edit the HTML,
CSS, and JavaScript of a web page. In this book, we’re interested in CSS.
Not to mention, when Style Master and Firebug were released so long ago,
websites were very simple, and we had only one screen size to test on. Today, a
website can be accessed by hundreds of smartwatches, mobile phones, tablets,
laptops, and desktop computers. Debugging for all of these types of devices
is not an easy task. You could ^x something for mobile and break it
unintentionally for the desktop.
It’s not only about screen sizes. ]e size of web projects has gotten much
bigger in the last 10 years. For example, the developers of a large-scale front-
end project like Facebook or Twitter need a systematic way to test and debug.
All of these changes to the work of web development are clear evidence that
debugging must be taken care of from day one and that developers must learn
it as a core skill.
We can use the same de^nition. Finding and resolving CSS bugs is an essential
Debugging CSS 5
1. Introduction and Overview
skill. Regardless of the programming language you are used to working with,
the debugging steps are almost the same for CSS. Later in this chapter, we’ll go
over a clear strategy for debugging that you can use right away.
When I refer to a “CSS bug” throughout this book, I mean a bug that was caused
by the developer, not a bug implanted by the browser. But we will address both
types.
If you need to ^x a bug quickly, you might feel a bit stressed, which could
lead you to rush a solution without a clear strategy. ]at could easily result in
confusion and time wasted on things that don’t matter.
Any programming language has logical and illogical errors. Take JavaScript.
When there is an error in your JavaScript, you can see by checking the
browser’s console. At least you will have evidence that there is an error, with
6 Debugging CSS
1. Introduction and Overview
CSS is completely di`erent. You won’t get any kind of alert when you’ve made
an error. ]at alone can make the simplest CSS bug very hard to ^x, if you
don’t think clearly and follow a sensible strategy. A CSS bug could be caused
either by the developer, as when a CSS property is improperly used, or by
inconsistencies between web browsers.
Moreover, you might be the one responsible for testing a website and
uncovering the bugs. So, we’re dealing not only with ^xing bugs, but with
^nding them as well.
Have you ever spent hours trying to solve a CSS issue, only to explain it to a
friend or colleague and gotten that spark of an idea of how to ^x it? ]at is
the e`ect of explaining a bug to a friend. You got stuck because you didn’t take
enough time to think deeply about the problem.
When you ^nd yourself going in circles like that, take a break and come back
to it later. Fixing issues needs intense focus. If you’re working on a solution
while your mind is exhausted, you might unintentionally break something
else. Avoid that with a break.
Debugging CSS 7
1. Introduction and Overview
In his excellent blog post, “You’ve Only Added Two Lines — Why Did ]at Take
Two Days!”, Matt Lacey presents some solid reasons for why debugging takes
time. Let’s go through them.
When working on an issue, it’s important to investigate the cause of it, not
only the symptoms. As Lacey says:
Making a bug invisible with a “quick ^x” might introduce some unexpected
side e`ects. You have a chance to ^x the issue, not to create more problems!
Some issues can be reproduced in multiple ways, not just the reported one.
Finding those ways is not only useful to thoroughly solving the issue, but also
can provide great insight into how the CSS is written, and whether there are
8 Debugging CSS
1. Introduction and Overview
other spots in the code base where the same issue can be expected to crop up.
]is can be very helpful for ^xing bugs before they reach users.
Fixing an issue is one thing; avoiding side e`ects from ^xing it is another.
]at’s why it’s best to ^x an issue with the least amount of CSS possible, and
with a thorough understanding of possible side e`ects.
Poorly organized code can make debugging much harder. For a large web
project, the CSS should be divided into components and partial ^les, which
would then be complied with a CSS preprocessor such as Sass, LESS, or
PostCSS.
If you decide to write all of you CSS to a single ^le, don’t expect debugging to
be easy. You will end up scrolling the large ^le up and down endlessly. ]is
approach is confusing and not ideal. More bugs tend to crop up in a single-^le
CSS.
In the next chapter, we’ll go through di`erent types of CSS issues, get into
details about debugging in the browser, and much more.
]is book is intended for designers and front-end and back-end developers
who are interested in improving their skills in ^nding and ^xing CSS bugs. You
should have an intermediate level of knowledge of HTML and CSS.
Debugging CSS 9
1. Introduction and Overview
In some sections, you will need to follow the steps of installing an npm
package. Don’t worry, that won’t require extensive Node.js experience. You’ll
be able to follow along easily.
]e lack of resources for learning how to ^nd and ^x CSS bugs is the primary
reason why I wrote this book. As soon as I began researching the topic, I
discovered that this topic has been overlooked. ]ere should be a guide that
discusses in an easy and straightforward way all of the details related to
debugging.
Now that we’re done with introducing the book, let’s start debugging CSS!
10 Debugging CSS
2. Introduction to CSS Bugs
What Is a Bug?
When something is di`erent from what you expect, that is a bug. For example,
an icon might not be aligned with its sibling elements, or an image might
look weird because it’s stretched (its width and height are not proportional to
each other). In some cases, what you view as a bug might actually be what is
expected. It could be a feature request, or someone has done it on purpose. For
this reason, it’s worth checking and asking the person about the bug in detail.
Web browsers are di`erent, and not all browsers support everything in CSS.
In the course of your work, you might encounter something that looks like a
bug in browser X, whereas in browser Y, it works perfectly. ]at doesn’t mean
that browser X is rendering it incorrectly. Sometimes, an issue occurs because
a browser vendor has implemented a feature according to the speci^cation,
whereas it works in another browser because that vendor didn’t implement it
incorrectly.
Let’s walk through the basic steps of what to do when you ^nd a CSS bug or
someone on your team points out something that is broken on a page you’ve
worked on.
First, check the CSS that is being used. Are you using some cutting-edge
12 Debugging CSS
2. Introduction to CSS Bugs
Go to Can I Use and check for browser support of the CSS property. If you see
that the property is not supported in the browser where the bug appears or
that the property is supported only with a vendor pre^x, then that might be
your answer. Make sure that vendor pre^xes are added (if any) and that the
browser you are testing supports the CSS property.
Once you establish that the CSS property can be expected to work in the
browser that is showing the bug, then it’s time to dig into the browser’s
developer tools (DevTools). Before inspecting the element, you will need to
determine what type of issue it is. For example:
In the next section, we will dig into the details of the types of CSS issues we
^nd and how to debug them using the browser’s DevTools.
Debugging CSS 13
2. Introduction to CSS Bugs
When you implement a design in HTML and CSS, any obvious inconsistencies
between the design and the code can be considered bugs. For instance, have
you ever noticed an icon misaligned with its text label, or that the page
container is either wider or narrower than the one the proposed in the design?
All of these can be considered visual design issues that the developer did
unintentionally.
]e designer might not know CSS, in which case they would probably take
screenshots of the issues and send them back to the developer with notes.
If the developer has a design background, then they might be able to easily
notice those inconsistencies reported by the designer.
14 Debugging CSS
2. Introduction to CSS Bugs
In the navigation design shown above, the ^rst one is the original design,
and the second one is the code implementation. ]e developer put e`ort into
implementing it, but it’s still far from the original design, for a couple of
reasons:
• ]e height is shorter.
• ]e font size is smaller.
• ]e border radius is less.
• ]e border color is di`erent.
• ]e shadow is too light.
We’ve already spotted ^ve ways in which the implementation is not similar to
the design. On a larger scale, a lot of components and sections will need to be
crafted carefully to make the implementation look similar to the design. Not
all developers notice these design details.
Moreover, issues with visual design include anything that poses an obstacle to
the user without being an actual bug. Some examples are an inaccessible color,
a confusing organization of content, a misaligned button, text that makes the
layout look weird, and inconsistent behavior between website pages. All of
these lead to visual design issues and, by extension, accessibility issues.
Debugging CSS 15
2. Introduction to CSS Bugs
Not all issues are noticeable just by looking at a web page. Sometimes you’re
dealing with a syntax error or an incorrect value for a CSS property. Let’s
explore the causes of technical issues.
Many a developer have spent hours trying to ^gure out why some CSS is not
working at all, only to realize that the cause is an incorrect path for a CSS ^le.
It could happen because you’re using a CSS preprocessor such as Sass or LESS,
which will render a .css ^le. Sometimes, the rendered ^le’s name is di`erent
from the source’s. Always be sure that the linked CSS ^le is the correct one,
especially if you have multiple CSS ^les.
Misnaming a Property
When you make a typo in a CSS property’s name, the browser won’t tell you
that directly. CSS doesn’t throw an error when something is wrong. You need
to ^gure it out by using the browser’s DevTools. If you inspect the element,
the browser will show the invalid property with a warning triangle and a strike
through the name.
.element {
opaciy: 0.5;
}
16 Debugging CSS
2. Introduction to CSS Bugs
]e reason I didn’t notice this trivial mistake is that I was so distracted and
didn’t think quietly about the reason for the bug. Most code editors will warn
you when a property name is mistyped. Here is an example from Visual Studio
Code:
Similar to the last issue, this one happens when you give an invalid value to
a CSS property. ]e value could be a typo or one that doesn’t work with the
given property. Consider the following example:
.element {
opacity: 50;
}
Not all properties work on their own. A property might depend on a certain
rule, applied either to the element itself or to a parent or child. Consider this:
Debugging CSS 17
2. Introduction to CSS Bugs
.element {
z-index: 1;
}
.child {
position: absolute;
}
Sometimes there is no typo or mistake, but you are overriding one property
with another. It’s just how CSS works, but some developers might think a bug
occurred. For example, CSS’ minimum and maximum sizing properties can be
confusing.
.element {
width: 100px;
min-width: 50%;
max-width: 100%;
}
18 Debugging CSS
2. Introduction to CSS Bugs
Here, the width of the element would be 50% of its parent. If you haven’t read
the CSS speci^cation carefully, you might think this is an issue, but it’s not.
Duplicating a Property
Sometimes you’ll declare a property, and for some reason it doesn’t have an
e`ect on the element. You keep trying and testing with no result. Eventually,
you realize that the property is duplicated, and you’re editing the ^rst
declaration of it, which is being overridden by the second one.
.element {
display: block;
width: 50%;
opacity: 1;
border: 1px solid #ccc;
opacity: 0;
}
• Maybe you copied some styles to test them quickly and forgot to remove
the duplicate.
• It could simply mean that you’re tired and need to take a break.
Your CSS could be 100% correct and valid, but one typo in a class name could
lead to styles not being applied to the element. As simple as this is, when we
are working for eight hours a day, we tend to focus on big problems and might
Debugging CSS 19
2. Introduction to CSS Bugs
CSS stands for Cascading Style Sheets. As indicated by the name, a website’s
styles cascade, and their order matters. If you de^ne a CSS rule for an element
and then rede^ne it at the end of the CSS ^le, the latter will override the
former.
]is is a very simple example of what can happen. You might face a trickier
issue than this. Consider an element that should switch colors on mobile and
desktop:
.element {
background: #000;
}
20 Debugging CSS
2. Introduction to CSS Bugs
CSS caching happens on the server, not on the local machine. A common
problem is pushing an update, and when you refresh the web page, the CSS
updates don’t appear. In this case, the CSS ^le might be cached, and you’ll need
to clear the browser’s cache or rename the ^le after each push.
]ere are multiple solutions to this problem, the simplest being to add a query
string:
And when you make a change, you would also change the version:
]en, the browser would download the latest CSS ^le. For more information,
CSS-Tricks has a great article.
Neglecting Performance
Using the wrong property for the job can easily impair performance. For
example, when animating an element from left to right, the left property is
a performance killer, because it forces the browser to repaint the layout with
each pixel moved.
.element:hover {
left: 100px;
}
Debugging CSS 21
2. Introduction to CSS Bugs
A better solution would be to use the CSS transform property. It won’t a`ect
performance, and everything will run smoothly. A simple choice of property
can signi^cantly improve performance!
.element:hover {
transform: translateX(100px);
}
Ignoring Specificity
If a CSS rule is not working as expected, the reason could be that its speci^city
is higher than another’s.
.title {
color: #222;
}
.card .title {
color: #000;
}
.card-title {
color: #000;
}
22 Debugging CSS
2. Introduction to CSS Bugs
As we’ve seen, there are many categories of CSS issues. Some are visual, and
others non-visual. Now that we’ve ^nished listing the common types, the next
step is to ^gure out how to debug, using the various tools and techniques at
our disposal.
Debugging CSS 23
2. Introduction to CSS Bugs
Once you have the browser’s name and version and gotten a visual of the issue,
you can start to debug. If you don’t have the browser that you need to debug
on, then you can either install it on your machine or use an online service,
such as BrowserStack.
Debugging Techniques
When it comes to testing a web page in order to debug CSS, there are a lot of
techniques and tools we can use, the most common being these:
• browser’s DevTools;
• mobile devices;
• mobile emulators (such as an iOS simulator);
• virtual machines (such as VirtualBox);
• online services (such as BrowserStack and CrossBrowserTesting).
24 Debugging CSS
2. Introduction to CSS Bugs
Wrapping Up
In this chapter, we’ve de^ned what a bug is, gone over the di`erent types of
CSS bugs, and summarized the debugging process. In the next chapter, we’ll
dig into the browser’s DevTools and learn how to leverage them when ^xing
CSS issues.
Debugging CSS 25
3. Debugging Environments and Tools
Every modern web browser has development tools, or DevTools, built in. In
the history section, I explained a bit about the tools Style Master and Firebug.
Browser DevTools are based on these projects. To open yours, right-click and
select “Inspect element” from the menu. If you’re a keyboard person, here are
the shortcuts for each browser:
I will be using Google Chrome in this book, unless I mention another web
browser.
You can inspect any element and toggle its CSS properties. To select an
element, right-click and choose “Inspect” from the menu.
When you select “Inspect”, the browser’s DevTools will open at the bottom of
the screen. ]at’s the default position for it. You can pin it to the right or left
side of the screen by clicking on the dots icon in the top right.
Debugging CSS 27
3. Debugging Environments and Tools
With the dots clicked, a little dropdown menu will open. You can choose where
to pin the DevTools. ]ere is no right place; choose based on your preference.
However, you will need to dock it to the right when you are testing at mobile
and tablet sizes. ]is is how it looks:
We’ve opened the DevTools and know how to access them. Let’s inspect an
28 Debugging CSS
3. Debugging Environments and Tools
element and play with its CSS at a basic level. With an element inspected, we
can toggle its styles with a checkbox (the checkbox is not visible by default).
With an element inspected, look over at the “Styles” tab. You’ll notice that
when you hover over a CSS property, a checkbox appears before the CSS
declaration. When this box is unchecked, the style will be disabled and won’t
be applied to the element.
When you toggle a style o`, the checkbox will be visible, to give you a visual
hint that it is disabled.
Debugging CSS 29
3. Debugging Environments and Tools
.menu {
/* display: block; */
}
In the “Elements” panel, you can select a CSS declaration that has a number,
and increment or decrement the value by using the up and down arrow keys.
You can also type a value manually.
You can also hold the Shift , Command , or Alt keys with the up and down
arrow keys to change numbers with set intervals:
30 Debugging CSS
3. Debugging Environments and Tools
]is is faster than changing one number at a time with the up and down
arrows.
When you change a value and want to exit editing mode, you can do one of the
following:
CSS Errors
]ankfully, Firefox has a great feature that shows a warning when you use a
CSS property that has no e`ect. At the time of writing, this feature is available
only in Firefox.
Debugging CSS 31
3. Debugging Environments and Tools
With the browser’s DevTools, you can test di`erent viewport sizes of the
website you’re working on. In this section, we will look at mobile testing topics
related to modern browsers (Chrome, Firefox, Safari, Edge).
Suppose you get a message from a client or colleague saying, “Hey, the font
size on page X is too small to read on mobile. Can we do something about it?”
]e ^rst thing we’ll need to do is ^re up the DevTools in the browser, and
switch to the device toolbar (in Chrome). You can access the device toolbar by
clicking on the mobile icon in the top-left corner of the DevTools or by using
the keyboard shortcut ( Command + Shift + M ). From there, we can start testing
di`erent sizes and, eventually, ^nd the source of the issue.
32 Debugging CSS
3. Debugging Environments and Tools
Other browsers, such as Firefox and Safari, have a device mode but call it
“responsive design mode”. Here is how to access it:
If an element has a width bigger than the viewport, then horizontal scrolling
will take e`ect. Try to scroll randomly to the left or right. ]is can reveal any
unwanted scrolling issues. Note: ]is book has a whole chapter on how to
break a layout.
Debugging CSS 33
3. Debugging Environments and Tools
While testing a website in mobile mode, the page will usually be very long,
and it wouldn’t be practical to have to keep scrolling to reach the element
you want to inspect. Luckily, Chrome has a feature named “Scroll Into View”,
which scrolls the page to the section you’ve selected.
]ere will be times when you need to take a screenshot of a page. ]e tools
available online are not all great. Chrome and Firefox have a feature to take
screenshots. I particularly like Firefox’s feature, named “Screenshot Node”,
which simply takes a screenshot of the selected HTML element. It’s very
helpful and a time-saver.
34 Debugging CSS
3. Debugging Environments and Tools
]e device pixel ratio (DPR) is the ratio between physical pixels and logical
pixels. For example, the iPhone 5 reports a DPR of 2, because the physical
resolution is double the logical resolution.
In mobile mode in Chrome’s DevTools, you will ^nd a dropdown menu for the
DPR. ]ere are two basic types of screens: standard and “retina”. A 1x image
will look OK on a standard screen but will look pixelated on a retina screen.
To simulate this e`ect on a standard display, set the DPR to 2 and scale
Debugging CSS 35
3. Debugging Environments and Tools
If you have a standard screen and a 1x image looks good to you, it’s possible
to simulate how it would look on a 2x screen by setting the DPR to 2 or by
choosing 2x as an option and then zooming in once.
In general, use SVG wherever possible. ]is can’t always be done, so if you use
images, provide di`erent resolutions for them. For example, you can use the
HTML <picture> element to load di`erent resolutions and sizes of the same
image. ]e browser will then serve a resolution suitable to the screen’s size.
]e user agent enables the server to identify the browser that the visitor is
36 Debugging CSS
3. Debugging Environments and Tools
Each browser also allows you to test di`erent user agents. If you’re on
Windows and using Chrome, you can switch the browser to “Safari on macOS”.
]e web server will identify the user agent you’re browsing with.
To debug and check the user agent of your current browser, open the DevTools’
console and type the following:
console.log(navigator);
Why debug this at all? Well, there are some important use cases. Consider this
^gure:
Another use case is a browser extension that is available in both Chrome and
Firefox browsers:
Debugging CSS 37
3. Debugging Environments and Tools
]e process of changing the user agent will depend on the browser you’re
using:
Media queries are the foundation of responsive web design. Without them, the
web wouldn’t look as it does today. To debug media queries, we need the power
of the DevTools.
First, inspect the CSS you’re debugging (in case you didn’t write it). Does the
code use min-width media queries more than max-width ? Do you see any
max-width media queries at all? ]is matters because of mobile-^rst design,
which you’ve probably heard of. It entails writing CSS for small screens ^rst,
and then enhancing the experience for bigger screens such as tablets and
desktop devices.
38 Debugging CSS
3. Debugging Environments and Tools
.nav__toggle {
/* Shown by default */
}
.nav__menu {
/* Hidden for mobile */
visibility: hidden;
opacity: 0;
}
.nav__menu {
visibility: visible;
opacity: 1;
}
}
However, if this wasn’t built mobile-^rst, then the menu toggle would be
hidden by default and shown via a max-width media query:
Debugging CSS 39
3. Debugging Environments and Tools
.nav__toggle {
display: none;
}
.nav__menu {
visibility: hidden;
opacity: 0;
}
}
When debugging a project, you need to get your hands dirty with such details
to know what you’re dealing with. ]is will help you to ^x issues more quickly
and reduce unwanted side e`ects.
To view a media query in Chrome’s DevTools, you need to select the element
that is being a`ected by it:
40 Debugging CSS
3. Debugging Environments and Tools
Notice that when we select an element, we see the media query for it. ]e
handy thing is that we can edit the media query and test right in the DevTools.
]e ^gure above shows a normal case, without any issue. Let’s explore the
most common bugs related to media queries.
]e viewport meta tag tells the browsers, “Hey, please take into consideration
that this website should be responsive?” Add the following to the HTML’s
head element:
Debugging CSS 41
3. Debugging Environments and Tools
.nav__toggle {
display: block;
}
Can you guess whether the .nav__toggle element will be visible in viewports
wider than 500 pixels? ]e answer is yes, because the second declaration of
.nav__toggle overrides the one in the media query.
42 Debugging CSS
3. Debugging Environments and Tools
.nav__toggle {
display: block;
}
When someone is reporting a bug, saying that a media query is not working is
not enough. However, we can check whether a media query is working with a
simple test. Suppose we have this:
To test the media query, we can add a background color to the body :
Debugging CSS 43
3. Debugging Environments and Tools
A common mistake is to use the same value in two media queries, one with
a min-width and the other with a max-width . ]is typically happens with
mobile navigation.
At a glance, these media queries might look good to you. However, 99% of
the time, you’ll forget to test an important breakpoint: 500px , that 1-pixel gap
between the two breakpoints. At this breakpoint, neither the navigation nor
the toggle would be visible.
44 Debugging CSS
3. Debugging Environments and Tools
]is 1 pixel is hard to debug without manually entering a value for a 500px
media query in mobile mode. To prevent this issue, avoid using the same value
in two media queries.
Debugging CSS 45
3. Debugging Environments and Tools
In Chrome, you can view a page according to the media queries de^ned in the
CSS, rather than according to the list of devices available in the browser.
As you can see, we have two bars, the blue bar for min-width queries, and the
orange for max-width queries. Having a broader view of all media queries is
useful for testing multiple query sizes. Conveniently, we can reveal a media
query in the source code. Right-click on a media query, select “Reveal in source
code”, and you’ll be taken to the line of code for that media query.
A common mistake with responsive web design is to test only by resizing the
browser’s width or by viewing multiple mobile sizes. However, decreasing and
increasing the height of the viewport are equally important.
Just as we have media queries tailored to the width of the viewport, the same
thing applies to height as well. Consider the following example:
46 Debugging CSS
3. Debugging Environments and Tools
After reducing the viewport’s height, we ^nd that the ^xed header is taking up
a lot of the screen’s vertical space. Notice how small the area available for the
content is. Users will be annoyed and won’t be able to easily use the website. A
simple solution would be to ^x the header only when there is enough vertical
space.
Box Model
If we remember anything about the box model, it should be that every element
Debugging CSS 47
3. Debugging Environments and Tools
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
I inspected a website’s logo to see its box model. Notice that you need to open
the “Computed” tab to see the box model. All boxes are labeled, except the one
48 Debugging CSS
3. Debugging Environments and Tools
for width and height. When debugging an element, looking at the box model
is extremely useful because it will show you all of the inner and outer spacings
of an element.
Sometimes, we might build a web page without a CSS reset ^le, and we’ll
wonder why some elements have certain spacing. Looking at the box model is
the best way to get an idea of what’s happening.
]e simplest way to see this for yourself is by going to your favorite website
and applying the outline property to everything:
Debugging CSS 49
3. Debugging Environments and Tools
*, *:before, *:after {
outline: solid 1px;
}
.element {
width: 50%;
}
]e width of .element is 50% , but how do we see its computed value? Well,
thanks to the DevTools, we can do that. Let’s dig into the “Computed” tab.
50 Debugging CSS
3. Debugging Environments and Tools
You’ll see that numbers have been assigned to various parts, to make it easier
to explain this area of the DevTools.
1. ]is is the property’s name. Usually, it’s colored di`erently from the value.
2. ]is is the value of the CSS property.
3. We can expand a property to see its inherited styles. ]is element has
font-size: 1.125rem , and it inherits a 1em font size from the html
element.
4. ]is is the pre-computed value, along with the element that the value is
attached to.
5. ]is is the name of the CSS ^le, and the line number of the CSS rule.
6. When hovering over the value of a property, an arrow will appear. Clicking
on it will take you to the source CSS ^le and the line number.
7. ]is ^lter helps with searching for a property. Note that you can only
search by a property’s longhand name. For example, searching for grid-
Debugging CSS 51
3. Debugging Environments and Tools
Grayed-Out Properties
You will notice that elements without an explicit height set might have a
grayed-out height property.
.nav__item a {
padding: 1rem 2rem;
}
]e <a> element doesn’t have a height set, but in reality, its height is the sum
of the content and padding, which is an alternative to an explicit height.
]is doesn’t happen only for padding. ]e example below has two elements,
one of which is empty.
52 Debugging CSS
3. Debugging Environments and Tools
<div class="wrapper">
<div class="element">content</div>
<div class="element"></div>
</div>
.wrapper {
display: flex;
}
.element {
width: 100px;
padding: 1rem;
}
Flexbox stretches child items by default. As a result, the height of the items
will be equal, even the empty one. If you inspect that element and check the
computed value of its height property, you will notice that it’s grayed out.
Tip: A property that is grayed out means that its value hasn’t been set
explicitly. Rather, it’s being a`ected by other things, such as the element’s
content or padding or by being a _exbox child.
Debugging CSS 53
3. Debugging Environments and Tools
What I particularly like about this is that you can hide all of the CSS ^les,
which is the equivalent of disabling CSS.
Also, being able to import a CSS ^le into a page is useful, opening up a lot of
possibilities. Imagine that you’re working on a layout for a web page and want
to change a few things here and there but without losing your work. You can
54 Debugging CSS
3. Debugging Environments and Tools
import a new CSS ^le, copy the current CSS to it, and then edit it as much as
you want. When you’re done, you can switch between the old and new CSS to
see the completely di`erent layouts.
.element {
z-index: 1;
}
If you haven’t changed the position of the element to anything other than
static , then it won’t a`ect the element at all. It’s not easy to spot these issues
while debugging because they don’t break the layout. ]ey are silent.
Firefox has a very useful feature for this, showing a warning when a CSS
property doesn’t a`ect the element it’s being applied to.
]is is very helpful and, at the time of writing, available only in Firefox.
Debugging CSS 55
3. Debugging Environments and Tools
While inspecting an element’s CSS, you can see the browsers that support a
particular feature, along with the browser versions. You can view details by
hovering over one of them. I like this feature a lot because it gives you hints
on which browsers to test more carefully.
It’s not enough to look at the computed value of an element. What’s more
useful is to ^lter a speci^c property that you need to check, and then resize
the responsive view wrapper to see the value change.
.title {
font-size: calc(14px + 2vw);
}
Here, we have a title with a base 14px font size plus 2vw (2% of the
56 Debugging CSS
3. Debugging Environments and Tools
I searched for font-size and then started to resize the view on the left. ]is
is a very helpful way to keep yourself aligned with what’s happening in the
background.
.element {
font-size: 1.5rem;
}
We’ve set the font size using the rem unit. To get the computed pixel value,
we would do this.
Debugging CSS 57
3. Debugging Environments and Tools
window.addEventListener('resize', checkOnResize);
function checkOnResize() {
const style = getComputedStyle(elem);
console.log(style.fontSize);
}
checkOnResize();
As you can see, this is the same concept, but with a resize event used this
time. ]is can be very useful for tracking things, and you can even render the
value on the page itself:
58 Debugging CSS
3. Debugging Environments and Tools
In Chrome’s DevTools, you can click and drag an element to reorder it. ]is can
be useful for changing the structure of an entire page or component. Once it’s
reordered, we can start testing various things, such as:
Debugging CSS 59
3. Debugging Environments and Tools
]is is how you would drag a section element along with its sibling:
We can also reorder child items. Suppose each section has a title and
description. We can reorder them inside their parent.
60 Debugging CSS
3. Debugging Environments and Tools
• inside its parent (this will just reorder it at the same level),
• between other parent elements,
• inside another parent element.
Debugging CSS 61
3. Debugging Environments and Tools
]ere are multiple ways to edit an HTML element in the DevTools. ]ey’re very
useful in cases where you want to add a class or attribute or maybe delete the
whole element. Here are the ways to do it:
CSS Classes
To add, edit, or remove a CSS class, you could double-click the class name of
62 Debugging CSS
3. Debugging Environments and Tools
the element, and it will become editable. But this is the less recommended
way to do it. ]e better way is to select the element, and then click the .cls
label with the DevTools opened. Being clicked, it will show all of the classes
associated with the selected element, and we can add or remove them by
checking and unchecking the boxes.
If the website you’re working on was built with utility-based CSS, debugging
its CSS in the browser would be di`erent than debugging a website with
regular class names.
Debugging CSS 63
3. Debugging Environments and Tools
<div class="card"></div>
When the whole website is built with utility classes, debugging will be a little
di`erent. Let’s say we want to inspect an element and remove display: flex
by unchecking the box in the DevTools. If we do this, any element that uses
the d-flex class will break. ]e reason, of course, is that we’ve removed the
display property from all of those other elements.
Using the .cls option would be better because it will list all of the CSS classes
for that element:
Another option would be to add inline CSS styles, which would override the
64 Debugging CSS
3. Debugging Environments and Tools
ones added in the CSS ^les. Or you could double-click on the element’s class
attribute and manually remove the class that you don’t want.
Say you have a div element but want to change its type without leaving the
DevTools. Well, this is possible. To change it, double-click the element type
and then edit the opening tag.
Note: ]ere is no needed to edit the closing tag. ]e DevTools will do that
automatically.
When you need to add an attribute, select the element, right-click, and select
“Add Attribute”. It’s that easy. Note that you can also add it by double-clicking
on the element itself.
Debugging CSS 65
3. Debugging Environments and Tools
Deleting an Element
To delete an element, hit the Function + Backspace keys. ]is will work in all
browsers and on all platforms. If you are using Chrome, hit the Backspace key
only. ]e mouse is an alternative: Right-click the selected element, and choose
“Delete” from the list.
Keyboard Goodness
66 Debugging CSS
3. Debugging Environments and Tools
The H Key
What’s the bene^t of hiding an element in this way? Here are a couple of uses:
Before digging into how to debug them, let’s review the pseudo-classes.
• :visited is the state when a link is clicked. When a user revisits a web
page, that link should have a di`erent state, so that the user can tell
Debugging CSS 67
3. Debugging Environments and Tools
• :active is the state when an element is being pressed from a click by the
mouse.
a:visited {
color: pink;
}
a:focus {
outline: solid 1px dotted;
}
a:hover {
background: grey;
}
a:active {
background: darkgrey;
}
If this order is not followed, some styles will get overridden. Order the styles
correctly to avoid issues.
Let’s get to the interesting part. How do we debug these pseudo-classes in the
DevTools? Well, there are two ways.
68 Debugging CSS
3. Debugging Environments and Tools
Select an Element
Right-click an element or click on the dots icon on the left, and then choose
“Force State”. From the options list, choose the state you want to activate. See
the ^gure below:
Checking the box adds a blue dot to the element on the left side. ]is visual
indicator shows that the element has a forced state.
Debugging CSS 69
3. Debugging Environments and Tools
1. Select an element.
2. Click on the + button in the panel.
3. A new rule will be added in the styles panel. All you need to do now is edit
it and add the pseudo-class you want.
70 Debugging CSS
3. Debugging Environments and Tools
In some cases, hovering over an element will add a child to the DOM.
Debugging these elements is tricky. ]ey will be hidden in the inspector
because you are not actively hovering over them.
In this case, the element we want to debug is already in the HTML but hidden
via CSS and only shown once its parent is hovered over. To debug this, the ^rst
thing we need to do is inspect the parent element. What’s the parent, you ask?
]is should clarify:
Debugging CSS 71
3. Debugging Environments and Tools
]is is more challenging. In this case, an element is added to the HTML only
when its parent is hovered over, and it’s removed completely from the HTML
when the user stops hovering. We’ll need help from the DevTools for this. To
debug, we need to freeze the website once the thing we want to debug has
become visible. ]e best way to do that is to pause the JavaScript’s execution.
When JavaScript execution is paused, it’s possible to follow the code that
toggles the menu. ]is way, we can catch the element once it appears, and
inspect it from there.
One important clue that indicates an element is being added to the DOM on
hover is that its parent element _ashes red. ]e _ashing means that this DOM
element is being edited through the addition or removal of a child item or
maybe the modi^cation of attributes.
72 Debugging CSS
3. Debugging Environments and Tools
Now, hover over the element that you want to debug. Once you do, press any
key on the keyboard, and you will notice that the application freezes, and the
thing you want to inspect won’t disappear. Feel free to dig in and inspect!
Debugging CSS 73
3. Debugging Environments and Tools
Break JavaScript
In Chrome and Firefox’s DevTools, you can break the execution of JavaScript
with any of the following:
• subtree modi^cation,
• attribute modi^cation,
• node removal.
Subtree Modification
]is targets child items of the selected parent. If any addition or deletion of
an HTML element happens, this is considered a modi^cation. In this case, the
browser will add a breakpoint.
74 Debugging CSS
3. Debugging Environments and Tools
Attribute Modification
]is watches for any modi^cations to the attributes of the selected element,
such as class names and HTML attributes. For example, a change to a class or
style attribute would cause the browser to add a breakpoint, and a menu would
then be shown via JavaScript.
Node Removal
]is is fairly obvious. Once an element is removed from the HTML, the
JavaScript execution would be paused.
In the JavaScript for the toggle button, you would add the following:
function showNav() {
debugger;
// Add a breakpoint once this function is called.
}
Once this function is called, the browser will add a breakpoint. ]en, you can
start debugging.
Note that if the browser doesn’t support the debugger keyword, this won’t
have an e`ect.
Debugging CSS 75
3. Debugging Environments and Tools
When you inspect an element and want to check its CSS ^le from the
DevTools, you might ^nd that the ^le has been mini^ed. ]is makes it hard
to read. ]ankfully, we have the little “Format Code” feature, which quickly
formats the mini^ed code.
Notice the opening and closing braces icon. One click on it and all of the code
will be formatted and easy to read.
76 Debugging CSS
3. Debugging Environments and Tools
]e only browser that allows you to copy the CSS styles of an element is
Chrome, even though it isn’t perfect. ]e latest version at the time of writing,
Chrome 81, has that feature. Here is how to do it:
1. Right-click on a element.
2. Select “Copy”.
3. ]en, select “Copy styles”. ]at’s it!
In the ^gure above, notice the di`erence between the original CSS and the
one copied from the browser’s DevTools. ]e copied one has inherited styles
such as box-sizing and font-family . Also, weirdly, it copied all of the CSS
properties in the document!
Debugging CSS 77
3. Debugging Environments and Tools
Rendered Fonts
Rendered fonts are the ones currently being used for a web page. To debug
them, inspect any text element, such as a heading or paragraph. At the bottom
of the “Computed” tab, there will be a section named “Rendered Fonts”. ]ere,
you can check the font applied to the element.
Also, as mentioned in the “Computed” section, you can search for font-
78 Debugging CSS
3. Debugging Environments and Tools
family and see the computed value of it. In addition, you can expand the
arrow next to it and see the CSS responsible for the addition of the font.
One useful feature in Chrome’s DevTools enables you to check for unused CSS.
It’s called “Coverage”. Here is how to use it:
Once it’s reloaded, you will see something like the following:
Debugging CSS 79
3. Debugging Environments and Tools
]e code blocks highlighted in red are the ones that are not used on the page,
while the ones in blue are being used. Also, it shows you the percentage of
the used CSS. ]at feature is extremely useful for refactoring CSS and checking
whether you have unused styles.
Chrome’s DevTools provide three types of color systems: hex, RGBa, HSLa.
When you pick a color for an element, it’s usually added as a hex color. What if
you want the RGBa value of that color without having to use a converter tool?
Well, that feature is available in the color inspector.
80 Debugging CSS
3. Debugging Environments and Tools
If you want a particular blue, and the design requires you to use it with
50% opacity, add the color as a hex value to the element and, with the color
inspector still open, click on the double-arrow icon on the right. ]is will
switch between hex, RGBa, and HSLa, very handy for quickly coverting a color
from one type to another.
When you edit the CSS of an element, you’ll probably want to copy and paste
it back in your code editor, instead of having to write it again. ]ere is more
than one way to do this.
In the following ^gure, I’ve added some inline styles to an element. Notice
how they’re added to the element.style selector in the DevTools. ]is feature
is the same for Chrome, Firefox, and Safari.
Debugging CSS 81
3. Debugging Environments and Tools
Now that we’ve added these inline styles, it’s possible to copy and paste them
into our code editor.
Firefox has a useful feature named “Changes” that shows the changes we’ve
made in the DevTools. It’s not unlike how version control shows the di`erence
between two changes. Here is how to use it:
82 Debugging CSS
3. Debugging Environments and Tools
When using a preprocessor such as Sass, the rendered CSS ^le might contain
instructions for a linked source-map ^le. When you inspect an element’s CSS
in the DevTools, you will notice that the CSS is in a ^le with the extension
.scss . ]is can be confusing.
To remove that .scss ^le from the DevTools, you will have to turn o` the
source-map feature; or you can open the “Sources” panel in the browser and
select the CSS ^le. ]en, you will ^nd something like this:
/*# sourceMappingURL=index.css.map */
]is is an instruction that makes the browser load the Sass ^le. Removing it
will hide the source-map ^le completely.
Debugging CSS 83
3. Debugging Environments and Tools
Even though most accessibility issues are caused by misused HTML, CSS plays
a role in accessibility, too. In this section, you will learn some things to keep
in mind when debugging CSS.
A color that is too faint to read will be a problem for users. According to
the Web Content Accessibility Guidelines (WCAG) 2.0, the foreground and
background colors should have a 4.5:1 contrast ratio at Level AA and a 7:1
contrast ratio at Level AAA.
To achieve this, use colors that are well tested. Great tools are out there to
make our job easier. To check whether a text color is accessible, inspect the
element, and click on the little color square. You will see a contrast number.
84 Debugging CSS
3. Debugging Environments and Tools
]e label should be hidden visually but not with display: none . Why?
1. You wouldn’t be able to tie the label to the input using the for
attribute.
2. A screen reader wouldn’t be able to read the input’s label. If you’re lucky,
it might read the placeholder, if one has been added.
]e correct way is to add a class of visually-hidden to the label. ]is will only
hide it visually and, as a result, won’t be an accessibility issue.
Debugging CSS 85
3. Debugging Environments and Tools
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap; /* added line */
}
Fixing small issues related to this can have a huge impact, and you don’t need
to be an accessibility expert to do it. Here is a realistic example:
We have an email input , without a label associated with it. If we inspect the
input and go to the accessibility panel, we will see something like this:
86 Debugging CSS
3. Debugging Environments and Tools
Notice that it says “textbox: Email address”, and it reads what’s inside the
input s placeholder. Without it, the ^eld would be empty, and that would be a
problem. Make sure to debug using the accessibility tree when you’re working
with web forms.
Of course, it’s not only about forms. ]ere are elements that shouldn’t be
exposed to users of screen readers — for example, a menu item with an
accompanying arrow.
Debugging CSS 87
3. Debugging Environments and Tools
.element {
pointer-events: none;
}
A simple CSS property can prevent a button from being clickable. Misusing
properties can ruin the experience, resulting in the loss of users.
Some CSS properties can cause performance issues when used incorrectly for
animation. ]e properties that any browser can animate cheaply are:
88 Debugging CSS
3. Debugging Environments and Tools
Using other properties for animation is risky and could impair performance.
Let’s go over how the browser calculates its styles. Here are the steps that the
browser takes:
1. Recalculate styles: Calculate the styles that are being applied to each
element.
2. Layout: Assign the width, height, and position of each element.
3. Paint: Paint all elements to layers.
4. Composite layers: Draw the layers to the screen.
Notice how busy the left timeline is. ]e browser keeps recalculating the
styles while the animation is happening. Meanwhile, translateX is very
di`erent; the browser’s work is light.
To check the performance of your web page, open up the DevTools, and select
the “Performance” tab. From there, you can start pro^ling and doing a test. A
pro^le is like a test that runs on the page for some time (usually seconds).
When it’s done, you can see a timeline with all of the details on how the
browser calculated the styles.
Debugging CSS 89
3. Debugging Environments and Tools
Our concern with the CSS is the recalculating and compositing. Avoid using
heavy CSS properties for animation.
You likely use di`erent browsers, each of which stores the history and private
information of your browsing. Debugging and testing websites in a browser
you use every day might not make sense. You’ll need something fresh, without
a history or cache, so that you can test without any unwanted issues, like CSS
caching, and to avoid extensions that might cause bugs.
For this reason, a dedicated browser pro^le for testing is recommended. Here
is how to create one in Chrome:
90 Debugging CSS
3. Debugging Environments and Tools
We can use a media query to edit the CSS styles and tailor the page to be
printed. To debug and emulate how a web page will look when it’s printed, we
can either print the page and save it as a PDF ^le or use the emulation feature
in Chrome.
Debugging CSS 91
3. Debugging Environments and Tools
@media print {
/* All of your print styles go here. */
.header, .footer {
display: none;
}
}
]e header and footer of a website might not need to be printed, so they can
be hidden with the print media query.
With iOS oacially supporting them, dark-mode user interfaces are rising in
popularity and becoming supported on the web as well. In CSS, we can use the
following to detect whether the user prefers dark or light mode:
.element {
/* Light-mode styles (the default) */
}
92 Debugging CSS
3. Debugging Environments and Tools
On macOS, you can switch between the dark and light mode of a website by
changing the system preferences.
While this works, it might not be practical when you’re working on a lot of
changes. ]ankfully, it’s possible to test that in the rendering settings. We
can emulate the media query prefer-color-scheme: dark or prefer-color-
scheme: light .
You can’t assume that all users will be ^ne with animation playing here and
Debugging CSS 93
3. Debugging Environments and Tools
there on your website. Some people prefer not to see animation on a page
because it can a`ect accessibility. A media query checks whether the user has
requested that the system minimize non-essential motion.
.element {
animation: pulse 1s linear infinite both;
}
@media (prefers-reduced-motion) {
.element {
animation: none;
}
}
Better yet, you can have simpler animation for users who prefer reduced
motion.
@media (prefers-reduced-motion) {
.element {
animation-name: simple;
}
}
With that, we’re done going over the browser’s DevTools. Let’s go over the
other methods we can use to test and debug CSS.
Virtual Machines
In the course of your work as a web developer, you will need to test in
browsers and on operating systems other than the ones you normally use.
For instance, you might use macOS but want to test on Chrome for Windows.
In this case, the cheapest solution is to use a virtual machine. I recommend
94 Debugging CSS
3. Debugging Environments and Tools
using VirtualBox because it’s free, easy to use, and works on both macOS and
Windows.
Also, Microsoft o`ers free copies of Windows to test in Edge and Internet
Explorer 11 browsers.
Online Services
Mobile Devices
Testing on real mobile devices can’t be compared to testing with the browser’s
DevTools. ]e reason is that it’s not possible to emulate a real device in the
DevTools. ]e touch screen itself plays a huge role in testing a website.
Tip: If your web project is run on localhost , you can open its link on any
mobile devices that are connected to the same Wi-Fi network. For example, if
the project is running on localhost:3000 , here is how macOS users can get
the full IP address:
Debugging CSS 95
3. Debugging Environments and Tools
3. On your mobile device, type the address with the port number (mine
would be 192.168.1.252:3000 ).
]en, you can access the project on your mobile device. From there, you can
update and edit the CSS to test it.
Mobile Browsers
]e browsers on mobile devices are di`erent from the ones we use on the
desktop. ]ey are simpler and more lightweight. On iOS, the default browser
is Safari. On Android, it depends on the phone’s manufacturer. For example,
Samsung phones have a preinstalled browser named Samsung Internet.
By connecting your phone to your computer via USB or USB-C, you can inspect
the code. For iOS, we can connect an iPhone and then inspect it with Safari on
the computer. ]is makes checking and testing faster. For Android devices, the
process is more complex. Follow along with this great resource from Chrome
DevTools blog.
Mobile Simulators
macOS developers can access the iOS simulator, where they can test on
multiple iOS device sizes (iPhone, iPad). Also, it’s possible to open the
DevTools for each device you test.
96 Debugging CSS
3. Debugging Environments and Tools
Browser Support
When starting a new front-end project, decide on the browsers you want to
support. For example, will you support Internet Explorer 11? Or an old version
of Firefox? Answer these questions ahead of time in order to prepare for what
is coming.
During the development process, you might accidentally use a CSS feature
that is not supported in the browsers you want to support. Because you
are designing according to progressive enhancement, you’ll need to check
whether the CSS feature is supported, and, if so, then you would apply the
feature as an enhancement.
Tools such as doiuse can be installed in your project via npm. Tell it the
minimum browsers to support. ]e sample command below would run in the
command line:
]e output would list the CSS features, along with warnings — for example,
that property X is supported only in Internet Explorer 11 and above.
Can I Use
Can I Use is a tool that is very useful for searching for speci^c CSS features. It
will tell you the history of the feature’s support. Sometimes, the support table
for a property will save you hours of trial and error when ^xing an issue.
Debugging CSS 97
3. Debugging Environments and Tools
Vendor Prefixes
Browser vendors add pre^xes for experimental web features that are still not
^nalized. In theory, developers shouldn’t use those properties on production
websites until they are 100% supported; then, they can use the unpre^xed
versions. However, many developers are not patient enough to wait years for a
property to be fully supported.
According to MDN:
]at means you won’t see any vendor pre^xes for future CSS features. ]at is
great; it will make new features much faster to ship.
MDN adds:
98 Debugging CSS
3. Debugging Environments and Tools
Wrapping Up
Next, we’ll explore some common CSS issues and learn how to solve them.
Ready?
Debugging CSS 99
4. CSS Properties That Commonly Lead to Bugs
Web browsers have evolved a lot in the last few years, which has resulted
in more consistent websites. Nevertheless, they’re still not perfect, and some
issues can confuse developers. Compounding the challenge are di`erent
screen sizes, multilingual websites, and human error.
As you implement a design, you might encounter CSS bugs for various reasons,
both visual and non-visual, which I covered in Chapter 2. Some of them are
hard to track.
In this chapter, we’ll dive deep into CSS properties and their issues. Our goal is
to get a detailed understanding of common bugs with certain properties, and
to learn how to solve them properly.
Box Sizing
If you’re not using a CSS reset ^le, you might forget to reset the box-sizing
property, which could cause one of the following:
• Horizontal scrolling
A block element takes up the full width of its parent. If it has a border
or padding, this will add to its width, which will result in horizontal
scrolling.
• Oversized elements
An element’s size can be bigger than you want it to be.
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
With that, we can be sure that the most important CSS property works as
expected. It’s worth mentioning that making box-sizing to be inherited is
better because it will enable all elements to inherit this box-sizing property
from the html element by default.
Display Type
When used incorrectly, the display type can cause confusion for developers.
In this section, we’ll go through some ways in which the display property
can go wrong.
Inline Elements
Elements such as span and a are inline by default. Suppose we want to add
vertical padding to a span :
span {
padding-top: 1rem;
padding-bottom: 1rem;
}
]is won’t work. Vertical padding doesn’t work for inline elements. You would
have to change the element’s display property to inline-block or block .
span {
margin-top: 1rem;
margin-bottom: 1rem;
}
]is margin won’t have an e`ect. You would have to change the display type
to inline-block or block .
<span>Hello</span>
<span>World</span>
]is will render Hello World . Notice the spacing between the two words.
Where did this come from? Well, because an inline element is treated as a
word, the browser automatically adds a space between words — just like there
is a space between each word when you type a sentence.
]e links are next to each other, with a space between them. ]ose spaces
might cause confusion when you’re dealing with inline or inline-block
elements because they are not from the CSS — the spaces appear because the
links are inline elements.
Suppose we have an inline list of category tags, and we want a space of 8 pixels
between them.
<ul>
<li class="tag"><a href="#">Food</a></li>
<li class="tag"><a href="#">Technology</a></li>
<li class="tag"><a href="#">Design</a></li>
</ul>
.tag {
display: inline-block;
margin-right: 8px;
}
You would expect that the space between them would equal 8 pixels, right?
]is is not the case. ]e spacing would be 8 pixels plus an additional 1 pixel
from the character spacing mentioned previously. Here is how to solve this
issue:
ul {
display: flex;
flex-wrap: wrap;
}
By adding display: flex the the parent, the additional spacing will be gone.
Block Elements
]e block display type is the default for certain HTML elements, such as div ,
p , section , and article . In some cases, we might need to apply the block
display type because an element is inline, such as:
input[type="email"] {
display: block; /* The element does not take up the full width. */
}
To override that behavior, we need to force a full width on the form element.
input[type="email"] {
display: block;
width: 100%;
}
]ere are replaced elements other than form inputs, including video , img ,
iframe , br , and hr . Here are some interesting facts about replaced
elements:
We have an image with display: block . Do you expect that it will take up
the full width of its container? It won’t. You need to force that by adding the
following:
img {
display: block;
width: auto;
max-width: 100%;
}
It’s worth mentioning that when an image fails to load, it’s not considered
a replaced element. You can actually add ::before and ::after pseudo-
elements to it:
img::after {
content: "The image didn’t load";
}
Have you ever noticed a little bit of space below an img that you’ve added?
You didn’t add a margin or anything. ]e space is there because the img is
treated as an inline element, which is similar to having a character with some
space below it.
If you are using fieldset to group form inputs, add a legend element. By
default, it won’t take up the full width of its parent unless you force it.
<fieldset>
<legend>What’s your favorite meal?</legend>
]e legend element is block-level, but its width will stay the same because it
has min-width: max-content by default, which means it has the width of its
text content. To make it full width, do this:
legend {
width: 100%;
}
.element {
position: absolute;
left: 0;
top: 0;
width: 100px;
display: block; /* Not necessary */
}
.element {
position: absolute;
left: 0;
top: 0;
display: none;
}
In this case, the element is hidden for small views and shown for large ones.
Using display: block in this way is OK.
A common issue in CSS comes up when you try to align an icon and text. Right
away, you notice that they are not aligned vertically. Either the icon or the text
is o` by a few pixels.
.icon {
vertical-align: middle;
}
Suppose you’re working on a layout, and you add an inline CSS style to hide an
element. Later on, you forget about it, and you head over to the CSS ^le to try
to make the element visible, and you add display: block . ]is won’t work,
because inline styles have a higher speci^city than rules in a CSS ^le.
p {
display: block;
}
If an element has float applied to it, then the browser will display it as a
block-level element, regardless of its type.
display: block property is greyed out. ]is means the display type is being
de^ned by the browser, not by us.
It’s worth mentioning that when you apply a float to an element with a
display type of flex or inline-flex , it won’t a`ect the element at all.
.element {
display: flex;
float: left; /* Has no effect! */
}
A less commonly known tip is that you can hide the br element with CSS. Say
you want two lines on mobile, and three on desktop. ]is can be easily done!
If you want to hide an element on the page, you might be tempted to use
display: none because it’s easy and quick. But in doing so, the element will
be completely hidden from screen readers. Let’s look at a couple of cases in
which you shouldn’t use the display property.
To Style a Checkbox
Styling a checkbox is possible in CSS. But you might run into an issue if you use
display incorrectly. Hiding the input completely from the page will impair
accessibility. Hide it visually only.
Margin
If two or more elements are close to each other on the page, the user will
assume that they are related to each other. ]e margin CSS property is
important in helping us make a design look more ordered and consistent.
Margin Collapse
]is is one of the most common issues with margins. Say you have two
elements, the one above with margin-bottom , and the one below with margin-
top . ]e greater of the two values will be used as the margin between the
elements, and the other will be ignored by the browser.
<div class="item-1"></div>
<div class="item-2"></div>
Mixing top and bottom margins leads to problems. To avoid this, use one-
Just-in-Case Margin
I call this a “just-in-case” margin because that’s what it is. Suppose we have
two elements:
We add a margin to the right or left side of one of the elements (in this case,
to the right of the title), just in case its content becomes too long and brings
the element too close to the adjacent one. If the title runs too long, the margin
prevents it from sticking to the icon.
Centering an Element
span {
width: 500px;
margin: 0 auto;
}
.element {
position: absolute;
left: 0; top: 0; bottom: 0; right: 0;
width: 120px;
height: 120px;
margin: auto;
}
Setting a width and height on the element makes it possible to center the
item with margin: auto only.
With _exbox, we can use an auto margin to push an element all the way over
in one direction.
]e button has the rule margin-left: auto , which pushes it to the far right.
Flexbox and auto margins work great together for such purposes. We can use
this technique to align an element without additional markup.
Padding
Suppose you have an element with a ^xed height, such as a button. Controlling
the vertical spacing within the element can be confusing because large
padding values might push the text downwards and break the button.
In the button on the left, notice how the text is pushed too far down. ]e
reason is because of this in the CSS:
.button {
height: 40px;
padding-bottom: 10px;
}
A button element should never be given a ^xed height. It will make things
complex and controlling the button will be harder. Instead, you should use
vertical padding .
You might encounter a case when dealing with a web font that has additional
spacing in its characters. In this case, you might need to tweak the top or
bottom padding in order to center the button’s text vertically.
.button {
padding: 3px 16px 8px 16px;
}
Here, we’ve tweaked the padding so that the text can be centered vertically in
the button.
]e shorthand for padding is ordered as top, right, bottom, left. It’s sometimes
confusing to use, and the same applies for margin as well. You could end up
doing the following:
.element {
padding-top: 20px 20px 0 20px;
/* … instead of: */
padding: 20px 20px 0 20px;
}
Remember to start from the top , and the rest will follow.
Percentage-Based Padding
Using a percentage for padding is OK, but to make it work as you expect,
remember that it works based on the element’s width.
.element {
width: 200px;
padding-top: 10%;
}
It’s worth mentioning that percentage-based padding for top and bottom was
treated di`erently for _ex items in old versions of Firefox. Firefox used the
height, rather than the width, of an element to determine the padding’s value.
]is issue got ^xed in Firefox 61.
Width Property
Setting width is one of the most important things in web design. We can set
a width explicitly or implicitly. In this section, we’ll go over cases in which
width might be confusing.
An inline element such as span won’t accept the width or height property.
]is can be confusing. An element accepts width and height only if its
display is set to something other than inline (such as inline-block or
block ).
needs to have a maximum width to keep the number of characters per line
easy to read. If you use a ^xed width for the text, you’ll notice horizontal
scrolling on mobile. I spent ^ve minutes wondering about the reason for the
issue, before identifying a ^xed width as the culprit.
img {
width: 100%;
}
With width: 100% , an img ’s width will be equal to its parent’s width.
However, sometimes we don’t want that behavior. ]ere is a better alternative,
which is to set max-width .
img {
max-width: 100%;
height: auto;
}
• A small image (say, 650 by 250 pixels) won’t take up the full width of
a wide parent (say, 1500 pixels). Imagine such an image taking up that
container! It would look pixelated.
• On the other hand, if an image is wider than the viewport, then its width
would be equal to 100% of its parent.
div {
width: 50%;
margin: 20px;
}
]is element’s width is 50% of its parent. And when the viewport is big
enough, we want it to take up the full width. Setting the width to 100% would
cause its contents to take up the full width of its parent without the margin
being calculated.
]is is a problem. To solve it, we should use auto instead of 100% . According
to the CSS speci^cation:
Setting the width to auto would result in the width of the content box being
the content itself minus the margin, padding, and border.
I’ve written a detailed article about auto in CSS that is worth checking out if
you want to dig more into the topic.
You might not think about this, but it’s interesting to know. Consider the
following:
<div class="media">
<img src="cool.jpg" alt=""/>
</div>
.media {
position: relative;
width: 300px;
height: 200px;
}
.media img {
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
}
You might expect that the image will take up the full width of its parent
because it is absolutely positioned against the four sides. Well, that would be
wrong. If the image is large enough, it will break out of its parent.
To prevent this from happening, set a width and height for the image.
.media img {
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%;
height: 100%;
}
Height Property
Setting a percentage-based height in CSS might seem intuitive at ^rst, but it’s
not. You can’t set a percentage-based height for an element unless the height
of its parent is explicitly de^ned.
.parent {
padding: 2rem;
}
.child {
height: 100%;
}
]e child won’t take up the 100% of its parent. Here is how to make it take up
the full height:
.parent {
height: 200px;
padding: 2rem;
}
]is way, the percentage-based height value of the child will be based on
something, and it will work as expected, even if using an absolute height
value is not recommended.
Let's suppose that we have a grid of cards, and we're using CSS grid to lay them
out.
<div class="media-list">
<div class="card">
<img class="card__thumb" src="thumb.jpg" alt="">
<div class="card__content">
<h2><!-- Title --></h2>
<p class="card__author"><!-- Author --><p>
</div>
</div>
<div class="card"></div>
</div>
.media-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(265px, 1fr));
grid-gap: 1rem;
}
By default, CSS grid will make the height of the cards equal, and that's useful.
But there is a problem, when one card has a longer title than the other, the
.card__content element height will be di`erent.
To solve this, we need to make the card as a _ex container, and then force the
.card__content to ^ll the available space.
.card {
display: flex;
flex-direction: column;
}
.card__content {
flex-grow: 1;
}
.card__content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.card__author {
margin-top: auto;
}
.avatar {
position: relative;
width: 25%;
display: block;
}
.avatar-aspect-ratio {
width: 100%;
padding-bottom: 100%;
}
.avatar img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
Notice that only the width property is being resized; the height will follow.
For more details about the technique, here is a great article on CSS Tricks.
We can use a viewport unit with width or height to make an element take
up the full width or height of the viewport. We’ll deal here with the viewport-
height unit.
body {
height: 100vh;
}
]is will make the body element take up the full height of the viewport.
However, Safari on mobile has a problem because it doesn't include the
address bar in its calculation, which results in a higher value for the height .
One solution is to get help from JavaScript by using the innerHeight method.
.my-element {
height: 100vh; /* Fallback for browsers that do not support custom
properties */
height: calc(var(--vh, 1vh) * 100);
}
By getting the innerHeight of the browser, we can use that value in the
height property.
.my-element {
height: 100vh;
height: -webkit-fill-available;
}
By using an intrinsic value for the height , the browser will ^ll only the
available vertical space. ]e downside is that this breaks in Chrome because
that browser also understands -webkit-fill-available and won’t ignore it.
My advice is not to use this solution until its behaviour is consistent in
browsers.
In CSS, we have the min-width and max-width properties. Let’s explore the
common mistakes and points of confusion that happen with them.
Minimum Width
When setting a minimum width for a button element, keep in mind that it
should work across multilingual layouts.
Here, we have a button with min-width: 40px . It works perfectly for English
layouts. However, when translated to Arabic, it becomes very small because of
its minimum width. ]is example is from Twitter’s website. ]e issue here is
having a very short width for a button, it will make it harder to the user to
notice it.
To prevent such an issue, always test with multiple kinds of content. Even if
the website is for one language only, testing with di`erent content won’t hurt.
]e reason for this is that the author is blindly depending on min-width and
has not considered whether the content might be longer.
.element {
width: 100px;
min-width: 50%;
max-width: 100%;
}
Resetting min-width
Setting to 0
]e default value of min-width is 0 , but this is di`erent for _ex child items.
]e min-width of _ex child items is auto , as explained previously.
Setting to initial
]e initial value will reset to the browser’s initial value, which would be
either 0 or auto , depending on whether the item is a _ex child.
Max Width
.wrapper {
max-width: 1200px;
margin: 0 auto;
}
]is might seem OK, until you resize the screen to be narrower than 1200
pixels. ]en you’ll notice that the child elements of .wrapper are stuck to the
left and right edges, which is not want we want. Make sure to add padding to
the page container so that it has a horizontal o`set on mobile.
.wrapper {
max-width: 1200px;
margin: 0 auto;
padding-left: 16px;
padding-right: 16px;
}
When using a percentage value for the maximum width, it’s common to forget
about it on mobile.
.element {
max-width: 50%;
}
Much better. ]e media query will activate the 50% width once there is
enough space.
will try to address them as both. Sometimes, you need to set a maximum width
based on the content you have. ]is can be tricky when the content varies. ]e
mistake is setting the width based on the content.
.wrapper {
max-width: 567px;
}
Using a hardcoded value like 567px in CSS is not a good practice because this
fails easily when the content changes. ]e solution is to use an intrinsic CSS
value.
.wrapper {
max-width: max-content;
}
]is way, the width of the wrapper will adjust to the content, without our
having to hardcode the value.
A common use case for max-width is to constrain an img not to be bigger than
its container. Because the img element is a replaced element, its size is based
on its content.
img {
max-width: 100%;
height: auto;
}
Resetting max-width
]e none value sets no limit on the size, which is exactly the goal of resetting
the property.
]is sets the property to its initial default value, which is none .
]e unset keyword resets the value to the inherited value if the property
I recommend using the none keyword because it’s the clearest, and you won’t
have to think through the consequences.
Minimum Height
A common challenge in CSS is setting a ^xed height for a section with content
that will change or that is inputted by the user. Setting a ^xed height might
break the section if the content goes too long.
Using min-height ^xes this. We set a value that would be the minimum
height, and if the content grows longer, the height of the section will expand.
Notice how the content over_ows the section vertically. ]is is because it has
a ^xed height.
section {
min-height: 450px;
/* … instead of… */
/* height: 450px; */
}
Setting min-height is better so that the modal can’t go below that value. ]us,
we’ll prevent any unwanted behavior.
.modal-body {
min-height: 250px; /* 250px is just an example. Tweak according to
your project’s needs. */
padding: 16px;
}
Maximum Height
Let’s start with this issue because it’s related to the previous one about modal
content. What if the modal’s content is too tall? ]e height of the modal will
become equal to the viewport’s height, which is not good.
So, we should use not only min-height , but also max-height , so that however
tall the content is, it won’t exceed the value we’ve set.
.modal-body {
min-height: 250px;
max-height: 600px;
overflow-y: auto;
}
]is one is also related. We set the maximum height in pixels, remember? ]is
will work, but it has a pitfall. What if the viewport’s height is too short and the
value of max-height is greater than it?
.modal-body {
min-height: 250px;
max-height: 90%;
overflow-y: auto;
}
]ere is a CSS solution that is kind of a hack, but it works. By using the max-
height , we can set a maximum value, and it will transition.
.element {
max-height: 0;
overflow: hidden; /* This prevents child elements from appearing
while the element’s height is 0. */
transition: max-height 0.25s ease-out;
}
.element.is-active {
max-height: 200px;
}
If an element has max-height: 90% , then it needs one of the following to work:
When you apply max-height with a percentage value, make sure one of the
conditions above is met. Otherwise, the computed value will be none .
As you can guess, a shorthand is the short version of a CSS property, and a
longhand is the long one.
.element {
padding: 10px;
}
]is is a shorthand property. ]is padding has four values, all of them being
.element {
padding: 10px 10px 10px 10px;
}
But because they are all equal, there is no need to write them out. ]e
longhand version looks like this:
.element {
padding-top: 10px;
padding-right: 10px;
padding-bottom: 10px;
padding-left: 10px;
}
.element {
background: green;
}
.btn {
background-image: initial;
background-position-x: initial;
background-position-y: initial;
background-size: initial;
background-repeat-x: initial;
background-repeat-y: initial;
… and so on…
background-color: green;
}
.wrapper {
margin: 0 auto;
}
.button {
padding: 10px 12px 15px 10px;
}
We might set the padding for a button in this way because it has a weird font
that is causing some alignment issues.
Positioning
CSS positioning issues often happen because of incorrect use of the position
property, whether because the author doesn’t completely understand it or
because of a plain old bug.
When using one of the properties top , right , bottom , or left , make sure
that the position is not static , the default value. If it is, then the o`set
properties won’t have any e`ect on the element.
Icon Alignment
.icon {
position: relative;
top: 3px;
}
We push the icon 3 pixels towards the bottom. Granted, we are hardcoding the
value here, but in this case it’s a valid use. ]e downside is that when the font
changes, the icon’s alignment might break, so be aware of that.
.element {
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%;
height: 100%;
}
]is element is already positioned to the four sides. It’s already taking up the
full space, so setting the width and height is not needed.
Reminder: ]is doesn’t apply to HTML replaced elements such as img . If the
element above was a large image, then the width and height would be needed,
otherwise you could expect horizontal scrolling.
A positioned element can have spacing that o`sets it from the four sides of
its parent element. If we want an element to have a 10-pixel o`set, then each
property of top , right , bottom , and left should have a value of 10px .
]ere are some tricky situations involving the padding and o`set properties.
Here we have a card with a footer that is o`set from the left, right, and bottom
sides by 12 pixels. How would we do that in CSS?
.card-footer {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
padding: 0 12px 12px 12px;
}
We position the footer to the four sides, and we rely on padding instead of the
o`set properties for the 12 pixels. ]is way, it’s easier to control when testing.
You might need to tweak the padding value.
Using z-index
.element {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
Setting the value to static will reset the value of position . When that is
done, keeping the values of top , right , bottom , left is ^ne because they
won’t have any e`ect.
<div class="home">
<!-- ::before element -->
<div class="floor"></div>
<div class="desk"></div>
<div class="laptop"></div>
<!-- ::after element -->
</div>
and the desk is on the _oor. ]at is, the last child will be positioned on
top of its siblings by default. Without understanding this, things might get
confusing.
]e same goes for pseudo-elements. In the HTML markup, notice how the
::before and ::after pseudo-elements are added to the .home element.
]e ::after element will appear in the layout on top by default, and the
::before element will appear under everything else in a normal stacking
context.
Some properties will trigger a new stacking context. ]e CSS speci^cation lists
the properties that trigger a stacking context. ]ey include a position value
other than static , opacity , transform , filter , perspective , clip-path ,
mask , and isolation .
<div class="elem-1"></div>
<div class="elem-2"></div>
.elem-1 {
position: absolute;
left: 10px;
top: 20px;
}
.elem-2 {
opacity: 0.99;
}
Which element will appear on top of the other? In this case, elem-2 will be
on top, because adding the opacity value will trigger a new stacking context,
For this issue, let’s start with the HTML ^rst, so that you can imagine it better.
<div class="wrapper">
<!-- other content -->
<div class="modal"></div>
</div>
<div class="help-widget"></div>
.wrapper {
position: relative;
z-index: 1;
}
.modal {
position: fixed;
z-index: 100;
}
.help-widget {
position: fixed;
z-index: 10;
}
From the markup, can you tell which element will be on top? It’s tempting to
think that the .modal element will be on top because it has a higher z-index ,
right?
<div class="wrapper">
<!-- other content -->
</div>
<div class="modal"></div>
<div class="help-widget"></div>
]us, we can position .modal . As a rule of thumb, if you have an element such
as a modal or popup, keep it outside of the page’s main wrapper, to avoid such
confusion.
One tricky case is when an element has a higher z-index than a ^xed header.
]is issue can easily trip us up because it isn’t easy to notice.
]ese cards have a blue element positioned absolutely in the top-left corner
(they might indicate the category of the card). When the user scrolls down,
the category will scroll above the ^xed header. Fixing this is simple: You just
.element {
width: calc(100%-30px); /* Invalid */
}
]is value is invalid. You must add spaces around the subtraction symbol.
.element {
width: calc(100% - 30px); /* Valid */
}
Text Alignment
Suppose you would like to add some CSS classes to an HTML button or to an
a link that is functioning like a button. A button ’s content is centered by
default, but an a element is not. So, you should center the latter manually.
.button {
/* Other styles */
text-align: center;
}
Without doing this, you might be surprised to ^nd later that some buttons on
your website are left-aligned!
Viewport Units
When you add height: 100vh to, say, a hero element, the elements within
it might look ^ne when the viewport is tall enough. I was once browsing
someone’s website on a 15-inch laptop. ]e hero section took up 100% of the
viewport’s height. It looked great!
I got curious and opened up the DevTools to see how it’s built, and — boom!
— the hero section broke. ]e elements within it overlapped the next section.
]e elements in the hero section didn’t ^t the available height once I opened
the DevTools. Why? It’s because when 100vh is used, opening the DevTools or
shrinking the browser’s height will reduce the height.
Speaking of which, the DevTools can be annoying when you’re testing for the
viewport’s height. I usually unlock the DevTools to a separate window when
debugging for the viewport’s height.
Pseudo-Elements
CSS pseudo-elements are one of the most useful additions to CSS. Misusing
them can be confusing, so let’s explore some common issues with them.
.element::before {
display: block;
background: #ccc;
}
]en, we wonder why the element does not appear. I’ve spent some time ^xing
such a bug. To avoid this, make sure that the content property is the ^rst
thing you add when creating a pseudo-element, before rushing on to other
properties.
.element::before {
content: "";
background: #ccc;
width: 100px;
height: 100px; /* Neither the width nor height will work. */
}
.row::before,
.row::after {
content: "";
display: table;
}
]is is the “clear^x” hack, which ^xes the layout of _oated elements without
the addition of presentational markup.
When _exbox is applied to the .row element, the two pseudo-elements will
be treated as normal elements, and that can create some weird spaces in the
layout. In such a case, the pseudo-elements wouldn’t have any bene^t, so they
should be hidden.
.row::before,
.row::after {
display: none;
}
<article class="card">
<img src="article.jpg" alt="">
<h2>Title here</h2>
</article>
We need to add a gradient overlay to make the text easy to read. ]e stacking
order of absolutely positioned elements (]e title and the ::after element)
starts from bottom to top. ]e element at the very bottom, the h2 , will appear
on top of the image. If we use ::after for the gradient overlay, it will be the
last element, which will put it on top of everything, so we would need to use
z-index: -1 to move it below the title.
However, if we use ::before , then the gradient would appear below the title
by default, without any adjustment to the z-index . ]us, we save additional
work and avoid a bug.
.card::before {
content: "";
/* The CSS for the gradient overlay */
}
Color
]e color property is an important one in CSS because it sets the color of text
elements. It might sound simple, but it’s not. Using it incorrectly can cause
problems and additional work.
]is behavior was seen in old versions of browsers such as Chrome and Safari.
To prevent this, avoid using the transparent keyword, especially with CSS
gradients. To solve the issue, it's recommend to use the following:
.element {
background: linear-gradient(to top, #fff, rgba(0, 0, 0, 0));
}
body {
color: #222;
/* All elements will inherit this color. */
}
However, the a element doesn’t inherit color by default. You can override
its color or use the inherit keyword.
a {
color: #222; /* … or… */
color: inherit;
}
]e hash notation that comes before a color’s hex value is important. I’ll
often copy and paste a color from a design app such as Adobe Experience
Design (XD) or Sketch. When coping from Sketch, the color is copied like
275ED5 , whereas Adobe XD adds the hash: #275ED5 . ]is di`erence can lead
to unexpected results if you are not 100% focused.
a {
color: 275ed5; /* Forgetting the hash */
color: ##275ed5; /* Doubling the hash */
}
Notice the hash incorrectly doubled in the second rule. While working on a
project, you might paste the color value with the hash, and then, after editing
the color in another app, you might double-click on the value (including the
hash) and blindly paste it back in the CSS, leading to a double hash.
Avoiding such an issue is possible with a style linter, of course. However, it’s
important to train ourselves to keep an eye when copying and pasting things
into a code editor.
CSS Backgrounds
In the shorthand of the background property, writing out the background size
and position can be confusing. ]ey have a certain order, separated by a slash.
If the order is missed, the background ’s entire de^nition will become invalid.
Notice the center/50px 80px . ]e ^rst one is for background-size , and the
second is for position . ]e order can’t be reversed. Spaces around the slash
are ^ne.
Dynamic Background
If the background is being set with JavaScript, use the dedicated properties for
background-size , position , and repeat . ]e background-image is the only
property that needs to be set dynamically with JavaScript. If you set the whole
background with JavaScript, that will be a lot of unnecessary work.
.element {
background: url("image.png") cover/center no-repeat;
}
.element {
background: url("image.png") center no-repeat;
background-size: cover;
}
CSS backgrounds are not included in print by default. We can override that
behaviour and force backgrounds to be included in print by using the following
CSS property:
.element {
background: url('cheesecake.png') center/cover no-repeat;
-webkit-print-color-adjust: exact; /* Forces the browser to render
the background in print mode */
}
CSS Selectors
Selecting an element by class name won’t work without the dot notation. ]is
often happens when we are not focused.
button-primary {
/* The styles won’t work. */
}
Grouping Selectors
Here is an interesting bug that you might not think about it. Grouping a valid
and invalid selector together can lead to the whole declaration being ignored.
a, ..button-primary { }
If just one of these selectors were invalid, the entire selector list would
be invalid.
A common mistake with CSS speci^city is calling a selector more than once.
]is alone is not a bug, but it easily opens the way for bugs. Avoid this pattern,
and use a style linter that warns about such things.
]e ^rst style will work with elements that have both .alert and .success
classes, while the second one will work only with elements that have both
.alert and .danger classes. However, suppose we did the following:
]e mistake here is adding a space between the two classes. ]is space
changes the whole thing, and it won’t work. We are basically selecting an
element with a .success class inside an element with an .alert class. It
assumes an HTML structure like the following:
<div class="alert">
<div class="success"></div>
</div>
Use the correct selector, or else you could waste a lot of time wondering why
it’s not working.
One way to prevent developers who you are working with from adding a class
to any element and calling it a button is by targeting classes together with their
elements.
button.btn { }
]is way, the .btn class won’t work on any element except the button . It’s a
good way to restrict usage of the class to actual button elements.
An Alternative to !important
Sometimes, a style won’t work because it’s being overridden by another style
in the CSS ^le. Using !important is not recommended. ]ere is a better way,
with CSS classes only.
.btn.btn { }
Calling a class twice increases the speci9city of a selector, thus making the
rule work without !important . Make sure there is no space between the
classes. Note that you can call it three, four, or however many times you like.
CSS Borders
Border on Hover
A common mistake when showing a border on hover is to add the border only
on hover. If the border is 1 pixel, then the element will jump by that much
when the user hovers over it. To avoid the jump, add the border to the normal
state with a transparent color.
]is way, the border has already been added and is reserving space, and the
appearance of the border on hover will be based on border-color .
We see this often with inline navigation menus, where items should have a
border on hover. Notice in the ^gure how the elements are slightly pushed to
the right once the navigation item is hovered over.
Multiple Borders
When you add more than one CSS border to an element — for example, borders
on the left and bottom edges — you might notice that the point where the two
borders meet is kind of weird. ]e bottom end of the left border and the left
end of the bottom border will look like cut-o` triangles.
]is is normal and expected. CSS borders work like that. If you want multiple
borders, then you could combine a border with a shadow to ^x the issue. ]e
left border can be kept as it is, and the bottom one can be a shadow.
.element {
color: #222;
border: 2px solid;
}
Notice that a color isn’t declared in the border rule. ]e default value is
currentColor , which inherits its value from the color property. Our example
could be written out as follows:
.element {
color: #222;
border: 2px solid currentColor;
}
]e point is that adding a color to the border rule is not necessary when it’s
]ere are many ways to transition a border with CSS. One common way is to
modify border-width . Suppose we have two buttons:
• ]e transition is slow. ]at is, the browser will not smoothly animate the
width. Instead of increasing it like 1, 1.1, 1.2 … 3 , it will increase like
1, 2, 3 . ]is is stepped animation.
:root {
--shadow-y: 3px;
}
.element {
box-shadow: 0 var(--shadow-y) 0 0 #222;
transition: box-shadow 0.3s ease-out;
}
.element:hover {
--shadow-y: 6px;
}
Going further, I de^ned a CSS variable to hold the y value of the shadow, and
I changed that on hover. Using a CSS variable, instead of copying the whole
box-shadow rule again, reduces redundancy in the code.
A border-width that works for laptop or desktop screens might be too big for
mobile. Usually, we would use a media query to change border-width at a
certain screen size. While that works, with the CSS tools available today, we
have better alternatives.
.element {
border: min(1vw, 10px) solid #468eef;
}
]us, border-width ’s maximum value will be 10 pixels, and it will get smaller
as the screen narrows.
When I started learning CSS, I thought it was possible to add a border to text.
It’s not. ]is might trip up anyone who is new to CSS. However, it is doable
with the text-stroke or text-shadow property. Let’s explore both solutions.
]e most common solution is to set color to transparent and then add the
border.
.element {
color: transparent;
-webkit-text-stroke: 1px #000;
}
While this works, the text will be inaccessible in unsupported browsers, such
as Internet Explorer and old versions of Chrome, Firefox, and Safari.
Both border: none and border: 0 will reset the border to its initial state. It
resets border-width to 0px and border-style to none , and border-color
will inherit its value from the color property.
Focus Outline
]is is not directly related to the border property, but it’s easy to confuse
border and outline . For example, a quick search on StackOver_ow for “css
border” returns a couple of questions whose titles contain “focus border, blue
border”. So, I decided to cover it here.
.nav-item a:focus {
outline: dotted 2px blue;
}
]e possibilities are endless. But please don’t remove that outline under any
circumstances, because it will a`ect the accessibility of the website.
Box Shadow
When you add a shadow in CSS, it will spread out from the four sides of the
element by default. I’ve seen a common request in my research for how to add
a shadow in one direction.
With box-shadow , the spread value controls the size of the shadow’s area of
coverage. By using a negative value, we can add a shadow to one direction of
the element.
.element {
/* The value of -5px is the spread of the shadow. */
box-shadow: 0 7px 7px -5px #ccc;
}
If you need to use overflow for an element, and some of its children have a
box-shadow property, the shadow will be cut o` on the left and right sides.
In this example, the thumbnail’s shadow is cut o` on the left and right sides.
]e reason is that overflow: hidden is being applied to the parent element
(the card). Avoid using overflow: hidden when you want a shadow to be
visible on child elements.
.element {
box-shadow: 0 5px 10px 0 #ccc, 0 10px 20px #222;
}
Do you remember when we talked about the display of an image and how
an image has a little white space below it? ]e reason is that it’s an inline
element. ]at also happens when we use box-shadow with the parent of an
inline image.
<div class="img-wrapper">
<img src="ahmad.jpg" alt="">
</div>
.img-wrapper {
box-shadow: 0 5px 10px 0 #ccc;
}
In this example, there is white space below the image, which becomes visible
only after the shadow is added. Make sure to reset the display value of the
When the website’s header is directly followed by, say, a hero image, then
adding a shadow might be tricky. If you try to add a shadow to the header, it
will be covered by the hero section.
Solving this issue can be done by changing the stacking context of the header
element. ]e easiest solution is to add the following:
.site-header {
position: relative;
}
A common design pattern for tooltips and dropdown menus is to add an arrow
that points to the parent element of the tooltip or dropdown menu. ]ere
are many ways to make an arrow in CSS, the most common being to create a
pseudo-element with a border on one side.
.element::before {
content: "";
width: 20px;
height: 20px;
background-color: #fff;
position: absolute;
left: 50%;
top: -10px;
transform: translateX(-50%) rotate(45deg);
box-shadow: -1px -1px 1px 0 lightgray;
}
img {
box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.2);
}
<div class="avatar-wrapper">
<img class="avatar" width="40" height="40" src="avatar.jpg"
width="40" alt="">
<div class="avatar-outline"></div>
</div>
.avatar-wrapper {
position: relative;
}
.avatar {
display: block;
border-radius: 50%;
}
.avatar-outline {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
border-radius: 50%;
}
.border {
stroke-width: 2;
stroke: rgba(0, 0, 0, 0.1);
fill: none;
}
Facebook uses both solutions in its 2020 redesign. ]e SVG one is used rarely
for things like pro^le pictures and user avatars in the sidebar. ]e solution
with the additional HTML element is used a lot in the social feed, in
comments, and more.
CSS Transforms
.element {
transform: translateX(10px) rotate(20deg);
}
However, sometimes you’ll need one transform on mobile and two on desktop.
Here, we run into a common issue.
.modal-body {
transform: translateX(-50%);
}
According to MDN:
right to left.
Notice how the order of the transform functions a`ects the visual position
of each rectangle. In the ^rst one, the element has been scaled ^rst, then
transformed 20 pixels to the right. ]e opposite happens with the second
rectangle. When debugging CSS transforms, make sure the order meets your
needs. Don’t add transform functions randomly.
When I started to learn CSS, I wasn’t totally aware that the transform CSS
property can include multiple transforms, and that we need to specify all of
the transforms we want every time we declare the property. ]e following is a
bug:
.element {
transform: translateY(100px);
}
.element:hover {
transform: rotate(45deg);
}
You might expect that both the translate and rotate functions will work,
but that’s not so. ]e second transform will override the ^rst one; thus, we’ll
lose translateY . Instead, we would combine them:
.element:hover {
transform: translateY(100px) rotate(45deg);
}
.element {
translate: 0 100px;
}
.element:hover {
rotate: 45deg;
}
Isn’t that simpler? You can detect whether this is supported with the
@supports media query.
]e coordinate system for HTML elements starts at 50% 50% . In SVG, it’s
completely di`erent; it starts at 0 0 . Because of this di`erence, using CSS
transforms with SVG elements can be confusing.
Taken from Ana Tudor on CSS-Tricks, the following example illustrates the
issue:
rect {
/* This doesn’t work in Internet Explorer and old versions of Edge.
*/
transform: translate(295px, 115px);
}
We can use the inline transform attribute for an SVG child element. It’s a bit
di`erent with the CSS transform, with no comma between the values.
I wouldn’t consider this a bug, but rather a question of ^nding a better way to
solve this need. Say we want to rotate a text element.
]e ^rst approach you might consider is positioning the text and rotating it.
While this would work, there is a better solution. By using CSS’ writing-mode ,
we can easily change the writing direction from left-to-right to top-to-bottom.
]e property sets the direction (horizontal or vertical) of a text element. It was
intended for languages such as Japanese and Chinese.
/* Without writing-mode */
.title {
position: absolute;
left: 40px;
transform-origin: left top;
transform: rotate(90deg);
}
/* With writing-mode */
.title { writing-mode: vertical-lr; }
With writing-mode , we can rotate the title with one line of CSS. Browser
support is great, too.
A scoped variable is one that can only be used inside an element, whereas a
global one, as evident by its name, can be used globally.
<div class="header">
<div class="item"></div>
</div>
.header {
--brand-color: #222;
}
We de^ne a variable, --brand-color , which will only work with the .header
element and its child items. An element with a class of .item can see the
variable.
.header {
--brand-color: #222;
}
body {
background-color: var(--brand-color);
}
]is will never work. ]e body element can’t see the CSS variable because its
scoped to the .header element. For this to work, the CSS variable must be
de^ned globally:
:root {
--brand-color: #222;
}
body {
background-color: var(--brand-color);
}
.title {
color: #222;
color: var(--brand-color);
}
]e ^rst rule above is a fallback for old browsers, which can be automated
using a tool such as PostCSS.
However, our focus here is on a fallback for the CSS variable itself:
.title {
color: var(--brand-color, #222);
}
If, for some reason, the variable --brand-color is not available, then the value
after the comma will be used instead. Note that you can use more than one
fallback value. See below:
.title {
color: var(--brand-color, var(--secondary-color, #222));
}
Sometimes, you might want to see all of the global CSS variables in your
application or website. ]ankfully, we can get them from the browser’s
DevTools.
Select the html element, and on the right side, you should see all CSS
variables de^ned within it.
]e ^gure highlights how CSS variables look when we inspect the root element
of the page (the html element). What I like about Firefox is that you can toggle
a variable! ]is can be very useful for debugging or testing the fallback value of
a CSS variable.
#toc {
position: fixed;
top: 11em;
top: max(0em, 11rem - var(--scrolltop) * 1px);
}
If the browser doesn’t support the max() comparison function, it will make
the property invalid at the computed-value time, which will compute to
initial ; and for the top property, the initial value will be 0 . ]is will break
the design. ]e ^x for this is to use the @supports function to detect support
for the max() function. If it’s supported, then the declaration will be used.
#toc {
position: fixed;
top: 11em;
}
Horizontal Scrolling
A little assistance worth highlighting is that Firefox shows a “scroll” label for
elements that are wider than the viewport. ]e label will guide you to debug
the element that is causing the horizontal scrolling.
When you click on the scroll label, Firefox will highlight the element that is
causing the horizontal scrolling.
]e h2 and p elements are causing the horizontal scrolling because they are
wider than the viewport. As a result, Firefox highlights them when the “scroll”
label is clicked.
Let’s focus ^rst on how to ^nd horizontal scrolling problems. ]e ^rst thing
to do is to make sure that the scrollbars are shown by default. macOS, for
example, doesn’t show the scrollbars until you start scrolling (either vertically
or horizontally). Making the scrollbars visible can help us to spot scrolling
issues much more quickly.
Go to “System Preferences” > “General” > “Show scroll bars” > “Always”.
On the page you want to test, try scrolling to the left or right with your mouse
or trackpad. Keep narrowing the screen, and repeat the process. If there is no
scrolling, then activate mobile mode in the DevTools. A horizontal scrolling
issue in mobile mode might look like the following:
]is means there is an element wider than the body or html element.
We can take it further and use a script to detect whether an element is wider
than the body or html element. ]is can be useful in a large project or one
you’re new to.
Here, we’ve selected all elements inside the body , checked whether an
element is wider than it, and printed it out.
Using outline
By using CSS’ outline property, we can add an outline around every element
in the layout. ]at can be a great help in revealing any issues. For example, it
can reveal whether any elements are taking up more space than allowed for
them.
*,*:before, *:after {
outline: solid 1px;
}
]is works perfectly, but we can take it to the next level with a script created
by Addy Osmani:
[].forEach.call($$("*"),function(a){a.style.outline="1px solid
#"+(~~(Math.random()*(1<<24))).toString(16)})
]is script will add an outline to every element on the page, with a di`erent
color for each one. (Having all outlines in the same color would get a bit
confusing in a complex layout.)
Note that using outline is much better than border , for a few reasons:
Now that we’ve identi^ed horizontal scrolling issues, it’s time to learn how to
debug them. When you ^nd horizontal scrolling, you might not see the cause
of the issue at ^rst glance, so you need to experiment.
Open up the browser’s DevTools and start deleting the main HTML elements
one by one, to see whether the scrolling disappears (Hint: you can use CMD +
z for macOS or CTRL + z or Windows to cancel the deletion of an element).
Once you see that the scrolling is gone, note the element that you just deleted.
Refresh the page, and dig into that element to see what’s there. ]ere could be
a few reasons for the horizontal scrolling. Let’s explore them.
A Fixed Width
A ^xed width will de^nitely cause horizontal scrolling. For example, the
following will cause a bug when the viewport is narrower than 1000 pixels.
.section {
width: 1000px;
}
To ^x this, we need to set a maximum width for the element using max-width :
.section {
width: 1000px;
max-width: 100%; /* Prevent the element from getting wider than
1000 pixels when the viewport is small. */
}
An element for which one of the position properties ( top , right , bottom ,
left ) is set to a negative value will cause a horizontal scrolling.
.element {
position: absolute;
right: -100px;
}
]e same thing can happen when you use a CSS transform to move an element
out of the viewport.
.element {
position: absolute;
right: 0;
transform: translateX(1500px);
}
If it's necessary to place an element outside its parent, then it's better to use
the following:
When using _exbox, the row won’t wrap by default. When the viewport gets
small, horizontal scrolling will happen because not enough space is available
to show all of the elements on one line. ]is is a common issue with _exbox.
To solve it, you need to force wrapping on certain screen sizes.
.section {
display: flex;
flex-wrap: wrap; /* Force flex items onto a new line in case not
enough space is available. */
}
When using CSS grid, there is a possibility of horizontal scrolling. Say we have
a grid with columns that are dynamic and that have a minimum width of 200
pixels.
.wrapper {
display: grid;
grid-template-columns: 200px 1fr;
grid-gap: 16px;
}
Everything looks good until the viewport gets narrower. ]e space isn’t
enough, and as a result, horizontal scrolling occurs.
To ^x this, we can apply the grid only when space is enough, using a media
query.
If an article has a very long word or link, it can easily cause horizontal over_ow
if it’s not handled properly.
As you see, the long word causes horizontal scrolling. ]e solution is to use the
overflow-wrap CSS property. It prevents a long word from over_owing its line
box.
.content p {
overflow-wrap: break-word;
}
It’s worth mentioning that the property has been renamed from word-wrap to
overflow-wrap .
If for any reason you’re not using a CSS reset ^le, then you need to make sure
that any image on the website doesn’t exceed its parent’s width. To do this, all
you need is the following:
You guessed it — forgetting to include that line will cause horizontal scrolling.
Transition
Transition on Resize
.element {
transition: all 0.2s ease-out;
}
]e all keyword tells us that the transition will be applied to all properties
of the element. ]is might be OK for one element, but using such a pattern
at scale is not recommended. When I started learning about CSS, I got used to
making the following mistake:
* {
transition: all 0.2s ease-out
}
]is bit of CSS adds a transition to every element on the page. Don’t do this,
please! It’s not a good idea.
Transitioning Height
.element {
height: 0;
transition: height 0.2s ease-out;
}
.element:hover {
height: auto;
}
.element {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.element:hover {
max-height: 300px;
}
I added overflow: hidden to clip any content that might be visible when max-
Here we have a menu that should be shown on mouse hover and keyboard
focus. If we used only opacity to hide it, then the menu would still be there
and its links clickable (though invisible). ]is behavior will inevitably lead to
confusion. A better solution would be to use something like the following:
.menu {
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
}
.menu-wrapper:hover .menu {
opacity: 1;
visibility: visible;
}
Overflow
When we have a component with a ^xed height and scrollable content, using
overflow-y: scroll is tempting. ]e downside is that when the content is too
short, a scrollbar will be visible on the Windows operating system. For macOS,
the scrollbar is hidden by default.
.section {
overflow-y: scroll;
}
To ^x this and show the scrollbar only when the content goes long, use auto
instead.
.section {
overflow-y: auto;
}
Scrolling on Mobile
When we have, say, a slider, it’s not enough to add overflow-x and call it a
day. In Chrome on iOS, we need to keep scrolling and moving the content
manually. Luckily, there is a property that enhances the scrolling experience.
.wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
where the content continues to scroll for a while after ^nishing the scroll
gesture and removing your ^nger from the touchscreen.
To solve this, change the alignment of the button that has overflow: hidden .
.button {
vertical-align: top;
}
Text Overflow
span {
text-overflow: ellipsis;
}
span {
display: block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
With these set, it will work as expected. Out of curiosity, I tested other display
types, including inline-block and flex , and none of them work as expected.
Using the !important rule without good reason can cause bugs and waste
your time. Why is that? Because it breaks the natural cascade of CSS. You might
try to style an element and ^nd that the style is not working. ]e reason could
be that another element is overriding that style.
Avoid !important in general. Here are some things to consider before using it:
• Try to identify the source of the speci^city issue with the DevTools.
• It’s sometimes warranted when you’re working with a third-party CSS ^le.
You might not have any option but to override the external style.
Utility CSS classes have become more popular recently. I would consider these
a good justi^cation for !important .
<div class="d-block"></div>
Flexbox
]e _exbox layout module provides us with a way to lay out a group of items
either horizontally or vertically. ]ere are many common issues with _exbox:
Some are done mistakenly by the developer, and others are bugs in a browser’s
implementation.
User-Made Bugs
Forgetting flex-wrap
When setting an element as a wrapper for _exbox items, it’s easy to forget
about how the items should wrap. Once you shrink the viewport, you notice
horizontal scrolling. ]e reason is that _exbox doesn’t wrap by default.
<div class="section">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Notice how the items aren’t wrapping onto a new line, thus causing horizontal
scrolling. ]at is not good. Always make sure to add flex-wrap: wrap .
.section {
display: flex;
flex-wrap: wrap;
}
]e grid of cards above is given space-between , but notice how the last row
looks weird? Well, the designer assumed that the number of cards would
always be a multiple of four (4, 8, 12, etc.).
CSS grid is recommended for such a purpose. However, If you don’t have any
option but to use _exbox to create a grid, here are some solutions you can use.
<div class="grid">
<div class="grid-item">
<div class="card"></div>
</div>
<!-- + 7 more cards -->
</div>
.grid {
display: flex;
flex-wrap: wrap;
margin-left: -1rem;
}
.grid-item {
padding: 1rem 0 0 1rem;
flex: 0 0 25%;
margin-bottom: 1rem;
}
Each grid-item has padding on the left side, but it’s not needed for the ^rst
grid item of each row. To avoid having to use complex CSS selectors, we can
just push the wrapper to the left by using a negative margin on the left side.
If we have a grid of six items, the last two will be added as empty spacer
elements. ]is ensures that space-between works as expected.
You might thoughtlessly type display: block because that is the common
way to show a hidden element. However, because the element is a _ex
wrapper, a display value of block could break the layout. ]is mistake could
lead to some debugging time.
Stretched Images
By default, _exbox does stretch its child items to make them equal in height if
the direction is set to row , and it makes them equal in width if the direction
is set to column . ]is can make an image look stretched.
<article class="recipe">
<img src="recipe.png" alt="" />
<h2>Recipe title</h2>
</article>
A simple online search reveals that this issue is common, and it has
inconsistent browser behavior. ]e only browser that still stretches an image
by default is Safari version 13. To ^x it, we need to reset the alignment of the
image itself.
While Safari version 13 is the only one that has the inconsistent behavior of
stretching the image, the button element is stretched in all browsers. ]e ^x
is the same ( align-self: flex-start ), but small details like this make you
think about the weirdness of browsers.
We see a related problem when a _ex wrapper has its direction set to column .
<div class="card">
<h2 class="card__title"></h2>
<p class="card__desc"></p>
<span class="card__category"></span>
</div>
.card {
display: flex;
flex-direction: column;
}
.card__category {
align-self: flex-start;
}
If the speci^ed _ex-basis is auto, the used _ex-basis is the value of the
_ex item’s main size property. (]is can itself be the keyword auto, which
sizes the _ex item based on its contents.)
Each _ex item has a flex-basis property, which acts as the sizing property for
that item. When the value is flex-basis: auto , the basis is the content’s size.
So, the child item with more text will — you guessed it — be bigger. ]is can be
solved by doing the following:
.item {
flex-grow: 1;
flex-basis: 0%;
}
With that, each child item will take up the same space as its siblings.
By default, _ex items won’t shrink below their minimum content size
(the length of the longest word or ^xed-size element). To change this, set
the min-width or min-height property.
]e person’s name is long, which causes horizontal scrolling. So, we add the
following to truncate it:
.c-person__name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.c-person__name {
/* Other styles */
min-width: 0;
}
Let’s walk through some of the most common issues with _exbox related to
incorrect or inconsistent browser implementation.
In this section, I will rely heavily on Flexbugs, a great resource by Philip Walton
for all browser bugs related to _exbox.
When using the shorthand version of the flex property, Internet Explorer
versions 10 to 11 ignore any calc() functions.
.element {
flex: 0 0 calc(100% / 3);
}
.element {
flex-grow: 0;
flex-shrink: 0;
flex-basis: calc(100% / 3);
}
In Internet Explorer 10, the calc() function doesn’t work in the longhand
flex-basis declaration. To work around this, we do the following:
.element {
flex: 0 0 auto;
width: calc(100% / 3);
}
<fieldset>
<legend>Enter your information</legend>
<p>
<label for="name">Your name</label>
<input type="text" id="name">
</p>
<p>
<label for="email">Email address</label>
<input type="email" id="email">
</p>
</fieldset>
fieldset {
display: flex;
flex-wrap: wrap;
}
You would assume that the inputs will be displayed next to each other, right?
]at’s not the case with this bug. It won’t work. A workaround is to wrap the
inputs in another element that can act as a _ex container.
<fieldset>
<div class="inputs-group">
<!-- inputs -->
</div>
</fieldset>
.inputs-group {
display: flex;
flex-wrap: wrap;
}
<div class="element"></div>
.element {
display: flex;
}
.element::before {
content: "Hello";
flex-grow: 1;
}
.element::before {
content: "Hello";
flex-grow: 1;
display: block;
}
In Internet Explorer 10, the !important rule doesn’t work with flex-basis in
the shorthand version.
.element {
flex: 0 0 100% !important;
}
.element {
flex: 0 0 100% !important;
flex-basis: 100% !important;
}
Centering a Flex Item With margin: auto Doesn’t Work With Flexbox Wrapper
Set to Column
You can use the margin: auto to center a _ex item in its container. In Internet
Explorer versions 10 to 11, this feature doesn’t work when the direction of the
_exbox wrapper is a column.
<div class="wrapper">
<div class="item"></div>
</div>
.wrapper {
display: flex;
flex-direction: column;
}
.item {
flex: 1 0 0%;
max-width: 25%;
}
]e expected result here is that the size of the elements would start from 0%
( flex-basis ) and won’t be more than 25% ( max-width ). We can achieve the
same e`ect by setting a value for max-width instead of flex-basis , and we
can let it shrink by setting a minimum size ( min-width for a row direction,
and min-height for a column direction).
.item {
flex: 0 1 25%;
min-width: 0%;
}
Firefox has some great resources for debugging _exbox components in its
DevTools. It shows a label of “_ex” next to each element that is a _ex container.
When an element is hovered over in the “Inspector” panel, the information
bar (the dark-grey one) shows the type of the _ex element.
]e great thing is that the “_ex” label is clickable. When it’s clicked, Firefox will
highlight the _ex layout items. It can also be accessed from the little _exbox
icon beside the CSS declaration in the “Rules” panel.
CSS Grid
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
}
.item-1 {
grid-column: 1 / 2;
}
.item-2 {
grid-column: 3 / 4;
}
]e .item-1 element has an implicit grid track, and it’s placed within the
grid’s boundaries. ]e .item-2 element has an explicit grid track, which
places the element outside of the de^ned grid.
CSS grid allows this. ]e problem is when a developer is not aware that an
implicit grid track has been created. Make sure to use the correct values for
grid-column or grid-row when working with CSS grid.
<div class="wrapper">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
.wrapper {
display: grid;
grid-template-columns: repeat(3, minmax(50px, 200px)) 1fr;
grid-template-rows: 200px;
grid-gap: 20px;
}
Keep that in mind when working with CSS grid. ]is issue might be confusing
at ^rst, but when you understand how it works, you’ll be ^ne.
You might think that the CSS grid fraction unit, fr , works as a percentage. It
doesn’t.
<div class="wrapper">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
</div>
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 200px;
grid-gap: 20px;
}
]e items look equal. However, when one of them has a very long word, its
width will expand.
<div class="wrapper">
<div class="item">Item 1</div>
<div class="item">I’m special because I have
averylongwordthatmightmakemebiggerthanmysiblings.</div>
<div class="item">Item 3</div>
</div>
Why does this happen? By default, CSS grid behaves in a way that gives the
1fr unit a minimum size of auto ( minmax(auto, 1fr) . We can override this
and force all items to have equal width. ]e default behavior might be good for
some cases, but it’s not always what we want.
.wrapper {
/* other styles */
grid-template-columns: repeat(3, minmax(0, 1fr));
}
Beware that the above will cause horizontal scrolling. See the section on
horizontal scrolling for ways to solve it.
]e unique thing about CSS grid that it has a fraction unit, which can be
used to divide columns and rows. Using percentages goes against how CSS grid
works.
.wrapper {
display: grid;
grid-template-columns: 33% 33% 33%;
grid-gap: 2%;
}
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 1rem;
}
I wouldn’t consider this a bug, but misusing auto-fit and auto-fill can lead
to an unexpected result. Let’s di`erentiate them ^rst. Take the following grid:
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 1rem;
}
Out goal is to have a minimum 200-pixel width for the grid item.
In auto-fill , the empty tracks won’t collapse to 0 , thus keeping the space
as it is.
In auto-fit , the browser will keep a minimum size of 200 pixels, and if space
is available, the empty tracks will collapse to 0 . ]us, the grid items will take
up the remaining space.
I was working on coding a layout of a new section for a client, and while
testing, I found a bug telling me that there was an empty space on the right
side. I opened up the DevTools and realized that I was using auto-fill for the
grid.
As you can see, this is not exactly a bug, but it a`ected the result and confused
me.
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
grid-gap: 16px;
}
If the viewport is narrower than 350 pixels, then horizontal scrolling will
occur. We can avoid that by setting up a media query.
.wrapper {
display: grid;
grid-template-columns: 1fr;
grid-gap: 16px;
}
]is way, the minmax() function will be applied only when there is enough
space.
Even though CSS grid is relatively new (being released in March 2017), it
can still get challenging, especially when supporting the old version of it
released in Internet Explorer 11. To avoid any issues, I recommend using
the @supports query to detect whether the browser supports the new grid
speci^cation.
I used grid-area because it’s a part of the new grid speci^cation. With this,
Internet Explorer 11 won’t apply CSS grid. Supporting grid in Internet Explorer
is not impossible, but you need to stick to its old implementation. Rachel
Andrew has written about the topic in detail.
When you code CSS, you’re writing abstract rules to take unknown
content and organize it in an unknown medium. - Keith J Grant
Notice how the text overlaps the icon. ]at’s because there is no padding on
the right. Fixing this is simple, but ^nding the bug before a user does is hard.
I will explain some techniques to prevent bugs from happening in the next
chapter.
]ere is more than one solution to this problem. ]e most common are:
<div class="card-meta">
<img src="author.jpg" alt="">
<div class="author">
<span>Written by</span>
<h3>Ahmad Shadeed</h3>
</div>
</div>
Solution 1: Float
To do this, we would need to _oat the image to the left and then add a clear^x
to account for the issue caused by _oats.
.card-meta::after {
content: "";
display: table;
}
Solution 2: Flexbox
]is will keep the image and text on the same line. However, we should
account for another scenario, which is if we don’t want the person’s name to
wrap onto a new line? In this case, text-overflow to the rescue.
.card-meta h3 {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
Wrapping Up
Now that we’ve reached the end of this chapter, I hope you’re more
comfortable with the most common CSS properties and their issues. Of course,
I haven’t mentioned every single property, but I’ve tried to include the things
that you will be addressing in your daily work.
If you’ve gone through the ^rst four chapters carefully, then you will be able
to tackle any CSS issue from the start to ^nish using the techniques you’ve
learned.
]is whole book is about ^nding CSS issues and solving them. Can we do the
opposite? In this section, we will explore di`erent ways to break a CSS layout
and make it fail. Yes, you read that correctly.
As we’ve seen, a common reason for a layout bug is text being longer than
expected. Adding long text randomly can reveal CSS issues that you haven’t
thought about.
]e article title in the ^rst box is short; the designer wrote it to make it ^t.
]e developer copied the text and implemented the component based on that.
When I tried to add long text, an interesting thing happened! ]e link icon was
pushed to a new line.
• Is this behavior intentional? ]at is, when the text gets long, should it
push other items to a new line?
forceFeed.js
]ankfully, tools exist to help us add random content and test for issues.
forceFeed.js is one of them. Let’s go through how it works.
Install
Include the script after the page’s content and before the end of the body
element.
<script src="path/to/forceFeed.js"></script>
</body>
Add the attribute data-forcefeed to the elements you want to set random
content on.
<div class="person">
<h3 class="name" data-forcefeed="words|2"></h3>
<p class="description" data-forcefeed="sentences|3|6">This will be
overridden</p>
</div>
window.sentences = ['Can you break me?', 'I love food and baking',
'How are you today?', 'When was the last time you saw mom?'];
With that, we can now refresh the page ( Command or Control + R ) to see the
content change. Perhaps you will notice a broken element.
If you’re working on a multilingual website, then the chances are high that
some design components will break when they have di`erent content.
We might have an English title that has modi^ed kerning (the spacing
between characters). It might work great, but when the translated page has
Arabic content in right-to-left (RTL) mode, the text breaks. Arabic has no such
thing as kerning.
Another bug can occur when we assume a speci^c minimum size for a button
component. When the content is translated into another language, it might
look di`erent.
On Twitter, the “Done” button looks good in English. In Arabic, the button
looks too small and is not easily noticeable. ]e reason is that the button has a
rule of min-width: 40px . ]is can be ^xed by increasing the minimum width
of the button.
]ese bugs related to language are important and shouldn’t be ignored. If you
are interested in learning more about the issue, I’ve written a complete guide
about it, RTL Styling 101.
]is is one of the easiest ways to break a layout and uncover its weaknesses.
When you resize the browser window, you’ll see some issues that you wouldn’t
normally notice. One interesting area to focus on is what I call “in-between”
design cases. I’ve written a detailed article about it on my blog, and I’d like to
go over the concepts again here.
In other words, you will uncover more issues when you test the in-between
design states. Believe me, you will ^nd some interesting issues that you or the
team haven’t considered.
Here we have some cards that need to be one column on mobile and two
columns on tablets. ]e in-between state makes the cards look too wide,
which can a`ect readability. While this might not seem like a bug, it is.
Another clear example of the importance of testing the in-between state is the
following footer design, taken from a real project.
In the middle view, the social media icon for Instagram breaks onto a new line.
Such behavior is not expected and shouldn’t happen at all.
]is should suace to illustrate that such issues can’t be ignored. ]anks to the
simple trick of browser resizing, we can discover them fairly easily.
Images play an important role in making web pages accessible and easy to
read. Your job as a front-end developer is to provide a solid structure for
a component that can handle any image used. For example, you might be
working on a hero section with a perfect cover image and accompanying text.
Breaking such a component is easy — just change the image. Suddenly, you’ll
see that the text is hard to read. We’ve forgotten to place a semi-transparent
black overlay that would make the text easy to read. Unfortunately, some
designers assume that the implementation of their design will exactly match
their mockup. ]at isn’t the case. A lot of changes will happen in the
development process.
Image sizes and dimensions are another area that deserves attention. As
the front-end developer, you might prepare a media component that can be
used for images in things like recipes and articles. One question to pose is:
What image dimensions are expected or recommended? Should the content
manager be restricted to uploading images with prede^ned sizes to the
content management system (CMS), or should they be free to upload whatever
sizes they like?
If you don't have much control over the image size, then I recommend using
CSS object-fit with a ^xed height for the image. ]at can keep all images
within the same height without breaking them.
.media__thumb {
height: 220px;
object-fit: cover;
}
Nope, this is not a joke. Internet Explorer is well known for breaking websites.
If your website is required to work in Internet Explorer, then you will need to
think about every CSS decision you’ve made. For example, if you’ve used CSS
grid for the layout, then it’s recommended to add a fallback using _exbox or an
older layout method (_oats, inline-block, etc.).
Wrapping Up
We all know that web browsers have inconsistencies, and that’s ^ne. As web
developers, we have to ^x those on day one of a project, so that we can
start with a clean and solid code base. In this chapter, I will go over the
most common CSS resets, along with how to make reduced test cases and do
regression testing.
CSS reset ^les are an important part of web development. ]e two most
common are Reset CSS by Eric Meyer and Normalize.css by Nicolas Gallagher.
By using a CSS reset ^le, you will save yourself a lot of time ^xing and
debugging issues that have already been ^xed. Take the following example
from Normalize.css:
/**
* 1. Correct the line height in all browsers.
*/
html {
line-height: 1.55; /* 1 */
}
Another example is making elements such as b and strong have a bold font
weight. ]is doesn’t work consistently in all browsers, so adding it will prevent
unexpected behavior.
/**
* Add the correct font-weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bold;
}
]ese are only a couple of examples. Remember to include a CSS reset ^le. If
you don’t want to, then at least work on your own reset ^le. Some will argue
that a reset ^le is not always needed because it will increase the total size of
your CSS ^les. I agree. But you can easily create your own small ^le and be
done with it.
Using Normalize.css
Compared to some other resets, Normalize.css only ^xes issues with browser
consistency, without resetting everything. It will keep common CSS styles
among web browsers. For example, the margins of heading elements such as
h1 and h2 will be preserved.
Here is a snippet from Normalize.css that resets the body element’s margin:
body {
margin: 0;
}
Depending on the nature of the project you’re working on, you can decide
what’s best to use.
Web browsers are made by humans, and humans make errors. It’s totally
normal to ^nd that a browser does not support a particular feature as expected
or implements a feature di`erently than other browsers. In this section, we’ll
walk through the steps of ^nding a bug in browser implementation.
First, what is a browser implementation bug? It’s a bug caused by the browser
itself. ]e bug might be in one or multiple browsers and is caused by improper
implementation of the CSS speci^cation.
Is there really a bug, or are you mistaken? It’s a waste of time to start
debugging something, only to realize later that it’s not a bug, but rather a
deliberate feature.
Some bugs are ones that appear in all browsers on all devices — those are easy
to ^nd. Other bugs appear in speci^c browsers or devices, like the Nexus 5
Android phone — ^nding those are harder because you would need an actual
Nexus 5 device or an online emulator, which is usually not free.
Verifying that a bug is indeed a bug will vary according to its complexity. Once
you are sure that it’s a bug, then you’ll move on to the next step.
Once you’ve veri^ed that it’s a bug, you’ll need to decide how the feature ought
to look and behave. For example, you could decide that a particular element’s
height should be between 150 and 450 pixels, and if it surpasses that, then it
Once you’ve veri^ed the bug and decided on the correct behavior, try to
reproduce it. Let’s learn how to reproduce a bug through test-case reduction.
Test-Case Reduction
One of the most underrated skills among web developers is creating a reduced
test case. When we encounter a problem with a web page we’re building, we
need to identify the cause. ]e problem might be across browsers or only on
mobile browsers. Debugging an issue by working with a page’s entire HTML
and CSS is not ideal. We need to isolate the problem in a test case, so that our
time is spent more on ^xing the problem than on identifying it.
1. Disable JavaScript
If you disable JavaScript and the problem is still there, then you’ll know
the problem has nothing to do with JavaScript. ]is is helpful for quickly
ruling out JavaScript-related issues. Adding on that, you can open the
project in a private mode tab, or deactivate all the browser extensions. If
the issue disappears, then it might be because a browser extension.
To make things clearer, let’s go through a real example of a CSS bug and see
how to convert it into a reduced test case.
In the ^gure below, we have a web page with a horizontal scrolling issue. We’ve
tried hard and can’t ^gure out how to solve the problem. So, let’s isolate the
problem as much as we can.
1. Remove any additional styles and scripts from the page’s head element
that are not important to the demo.
2. Inspect the body , and delete the sections one by one. If deleting a section
causes the issue to disappear, then keep it.
3. Once we’ve decided on the HTML that should be kept, the next step is
the CSS. We need only the CSS required to make the demo work, such as
width , height , and display . Decorative properties such as background ,
border , color , and box-shadow can be removed if they don’t a`ect the
issue.
Following the steps above, we should have the least amount of HTML and CSS
to make the test case work. Repeat the steps above, and make sure that the
code is clean. Here is how our reduced test case looks after we’ve isolated it:
It looks like the issue is in the grid section. Now, the question is, is it possible
to reduce this even more? Here is what we can do:
Once it’s cleaned, we can add some comments to aid with testing.
]e comment makes it helpful for us or anyone else who tries to ^x the bug.
]is is the most we can do to reduce the test case. ]e next step is to extract
the HTML and CSS, and upload them to our website or wherever we want.
Make It Fail
In his book on debugging, Dave Agans identi^es “make it fail” as one of the
primary steps in debugging any coding problem. ]e extent to which we can
follow this advice with CSS will depend on the type of bug we’re dealing with.
Some bugs are clear — for example, ones that appear in all viewport sizes.
Other bug types are more complex, in which case making them fail is harder.
As Agans mentions in his book, the reasons for making it fail are so that:
]e toast burns only if you put bread in the toaster; therefore the problem
is with the bread.
toaster’s. ]is same goes for CSS development. If we use a layout module to do
something it wasn’t designed for, then the fault is ours.
You might wonder, “What if I’ve tried everything I know of and still can’t
reproduce the issue?” Well, remind yourself that every failure has a cause.
]ere is no secret recipe for ^nding it; it’s hidden somewhere in the
randomness.
Before testing, save your work in a new Git branch. If you don’t use version
control, copy your work in a backup, and start testing from there. By doing one
of these, iterating and changing things will be much safer, and the chances of
losing work will be very low.
Document Everything
1. what I did,
2. the order of steps I took,
3. what happened as a result of the steps taken.
Documenting these steps can be helpful for you and your team.
When doing the steps above, the page should be blank, except for the header,
where the problem is.
Once we have a reduced test case, we can start testing the bug in the browser
or device in question. We would keep iterating and editing until we notice a
di`erence.
When iterating, it’s very important to change one thing at a time. Don’t
change the CSS randomly and hope that it will work. If it works, then you
won’t know how you did it, and the guesswork will start. Change one thing,
test, and repeat.
]e usefulness of this approach is that, when the bug is ^xed, we can compare
the changes we made to make it work. ]is wouldn’t happen if we changed a
million things at once to get it working.
If you’ve tried hard to ^x the issue and can’t do it, then the internet is your
friend. Search online for the issue or pattern, and see whether others have
faced the same issue. Chances are high that you are not the ^rst.
If you believe that the bug you’ve found is unique and no one has ever
encountered it, then it’s time to report it to browser vendors. Every browser
vendor has a public forum with all of the bugs submitted by users. If you’ve
documented the steps taken to reproduce the issue, as recommended earlier,
then all you need is to post the steps with your reduced test case ^les. Also,
submit a screenshot or video if that would help.
• Firefox: bugzilla.mozilla.org
• Safari: bugs.webkit.org
• Chrome: bugs.chromium.org
When working on a reduced test case, you might create multiple copies of
it, each with a slight change. Don’t delete them. Archive them, because they
might be helpful in the future. You could do a few things with them:
• Write a blog post about the bug and your test cases, explaining how you
^xed it.
• Keep them as a log for yourself, for when you face a similar bug.
• Share them with a colleague or team members who want to learn how you
^xed it.
Regression Testing
As explained on Wikipedia:
When you ^x a bug, you might accidentally break another thing without
knowing it. ]at is called a regression. Testing for regressions can be time-
consuming, because a bug might occur in a particular environment, viewport
size, or scroll position. Using a tool, we can do regression testing by de^ning
our design components.
BackstopJS uses a headless Chromium browser, the same one used for Google’s
Chrome. Here is how it works:
$ backstop init
When we initialize the project, a backstop.json ^le will be created in the root
directory of the project. You will ^nd everything that can be con^gured in
there. For our simple lesson, the following are required:
• id
• viewport
• scenarios
In the scenarios array, there is a selectors array, where we can add all of
the CSS selectors to watch. I’ve added the following:
"viewports": [
{
"label": "phone",
"width": 320,
"height": 480
},
{
"label": "tablet",
"width": 1024,
"height": 768
}
],
"scenarios": [
{
"url": "http://localhost:8080/"
"selectors": [
".c-header--full"
]
}
]
In this con^guration, we’ve added two elements to watch, and the URL of the
page is index.html . ]e next step is to generate the reference screenshots.
$ backstop reference
To use the tool, let’s increase the vertical padding in the header, and then rerun
the test.
$ backstop test
]e test failed because the reference screenshot is di`erent from the tested
one. Notice how the third screenshot has a pink color — this is a highlight of
the di`erence.
Wrapping Up
In this chapter, we’ve learned about CSS resets, reduced test cases, and
regression testing. We’re almost done! In the next chapter, we will explore
some general tips and tricks for debugging CSS.
Spacing Issues
When debugging for LTR and RTL, most of the issues will be related to spacing.
]e horizontal direction will be _ipped for each language, and the spacing
issues will usually come down to either padding or margin . Say we have the
following:
.element {
margin-left: 10px;
}
.element {
margin-right: 10px;
}
We would do the equivalent for padding and the positioning properties ( top ,
right , bottom , left ).
Adding on that, we can use CSS logical properties to avoid writing more CSS
for RTL. Here is how the above example will look:
.element {
margin-inline-start: 10px;
}
Alignment Issues
.element {
text-align: right;
}
.element {
text-align: left;
}
Debugging RTL
Depending on how the website you’re building works, switching the CSS from
LTR to RTL for a given page might be easy. If the CSS is combined into one ^le,
switching will be as easy as setting the dir attribute on the html element.
<html dir="rtl"></html>
We can ^rst set the attribute in the DevTools, and then inspect the issues we
want to ^x.
If the CSS for the LTR and RTL isn’t in one ^le, then it is most probably in two
^les, such as main-ltr.css and main-rtl.css . Switching the dir attribute
won’t be enough then; we would also need to edit the src of the style sheet
in the head element.
Let’s say we’ve built the CSS for the LTR and RTL layouts, and the only thing
missing is to test the typography of the RTL content. When viewing the design
in RTL mode with the LTR content, you can use Google’s in-page translation to
quickly translate all of the content. ]is will help you to create an RTL design
with the content and make it suitable for the text direction.
I’ve written an extensive guide about this, in case you’re interested, titled RTL
Styling 101.
Using @supports
In case you don’t know about it, @supports is used to detect whether a given
CSS feature is supported by the user’s browser.
I added the letter “B” after display: flex . ]e browser won’t recognize that,
and you will get the default behavior, as if @supports is disabled. Cool, right?
]e extension will add a new tab in your browser’s DevTools. On the left, you’ll
see a toggleable list of the CSS features nested in @supports queries, and on
the right will be a list of every @supports query that uses a particular feature.
]e CSS shown above is for grid-related stu`. Toggling the checkbox on the left
will disable and enable CSS grid. ]is is a great way to test and break a layout.
Let’s get more into the ways to break a layout.
Browser Extensions
Grid Ruler
A great way to test whether two UI elements are aligned correctly is to use
a ruler and guides. ]is can be easily done in design apps such as Sketch,
Adobe XD, Photoshop, and Illustrator. In a browser, it’s not possible without an
extension.
One great extension, Grid Ruler, is available only in Google Chrome. It enables
you to drag and place guides either horizontally or vertically. ]is is extremely
useful for verifying that two elements are aligned correctly.
In this mockup, the grid line tells us that the user avatar and the button are
aligned.
]e OLI Grid CSS plugin is available for Firefox and Chrome. What’s nice about
it is that it draw in the page columns, just like in Sketch and Adobe XD. ]is is
helpful for seeing whether the layout you’re working on aligns to the columns.
I tried testing the plugin with a Bootstrap-built page, and it works as expected.
Note that you need to ^gure out the width of the .container element of the
page ^rst.
A very useful extension that provides a lot of functionalities to do. Here are
some key things:
Pesticide Extension
]ere are times when you want to quickly mock up a design idea in the
browser by moving a few elements here and there. ]is is useful for showing
a design concept to a developer, client, or designer. Being able to make such
edits quickly is important to productivity.
With CSS positioning, we can edit some elements in the DevTools by adding
position to them and placing them where we want. ]is is a quick way to
mock up a design idea while testing for bugs.
Here we have a card with a category. After some thinking, the designer tells
you, the developer, that the team has decided it wants a di`erent position for
the category. You suggest that the category could be moved to the upper-left
corner. ]is can be done while you both are on a video call. It’s as simple as
adding the following:
.card {
position: relative;
}
.category {
position: absolute;
left: 0;
top: 16px;
}
]is kind of edit, which didn’t take a minute, can allow decisions to happen
more quickly.
Here we have a section header, which contains a bug that prevents the
author’s avatar and name from aligning. ]e design team has requested that it
be removed temporarily. You can quickly delete it from the HTML, hide it with
display: none , or use the H key in Chrome.
CSS Flexbox
]is section header has a row of items. ]e problem is that the spacing
between items is inconsistent. What can we do? ]e fastest solution is to add
display: flex and justify-content: space-between . ]e design is changed
instantly, and all of it happens in the DevTools! You can now proceed to
screenshot this change and discuss it with your colleagues.
]is is the most powerful layout module in CSS. Suppose we have a featured
news section, and the designer wants to lay out the items in a presentable way
— say, as equal-height columns.
We simply use CSS grid to set the columns, and then we con^rm with the
designer that this is what they want.
.wrapper {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
}
Is that still not enough for the designer? You can continue editing and showing
them your changes. Moreover, you can try di`erent layout concepts and tie
each one to a CSS class, toggling each class in the “.cls” panel.
We can use viewport units to make a section take up the full horizontal or
vertical space of the viewport. We can also use them to size fonts. All of these
use cases give us _exibility and make our designs more dynamic.
Suppose we have a hero section that is required to occupy 90% of the screen’s
full height. We want to verify the requirement with the designer, so we mock
it up very quickly:
.hero {
height: 90vh;
}
We give the hero section a height of 90vh , which will make it occupy 90% of
the screen’s vertical space. We made this edit in less than a minute!
CSS Columns
If we want a quicker method than CSS grid, we can use columns. For example,
we could divide links in the footer into two equal columns. We can make this
edit with one line of code, and get back to the designer right away.
.footer-section {
columns: 2;
}
]e other good thing about CSS columns is that we can change the number of
columns with the keyboard’s up and down arrows.
CSS Filters
Let’s say the designer wants to experiment with a dark mode for the website,
but they haven’t done any mockups for it. Using CSS ^lters, we can quickly
whip up a dark mode.
html {
filter: invert(90%) hue-rotate(25deg);
}
And to polish it, we can revert the elements that shouldn’t have been inverted
(such as images and videos):
html {
img, video, iframe {
filter: invert(100%) hue-rotate(-25deg);
}
}
With that done, we can take a full-page screenshot and show it to the team —
all in less than two minutes! Isn’t that cool? With the mockup sent, the team
can start thinking and deciding on it. Also, you’ve saved the designer’s time!
Making a dark mode for a small web page would take them at least 10 minutes.
Desaturating a page (i.e. converting it into black and white) using CSS ^lters is
a useful trick, for a few reasons:
• If the website you are testing is heavy with colors, your eyes might get
tired. Desaturating the page will help you focus on ^xing the bug at hand.
• It’s useful for testing and exploration. When the page is saturated, you can
easily spot any colors that are not suitable for the design.
• Testing for accessibility becomes easier. Making the page grayscale will let
you know which colors are easy to read and which are not.
To desaturate a web page with CSS, open up the browser’s DevTools, select the
html or body element, and add the following:
html {
filter: grayscale(1);
}
Wireframe Styling
When mocking up a design, we don’t always have time to choose good colors
and fonts. In this case, we can convert the whole web page into a wireframe
style using a bit of CSS. ]is will let you focus on mocking up ideas quickly and
getting feedback as soon as possible.
* {
color: #000;
background: #ccc !important;
outline: solid 1px;
}
While debugging on touch devices (phones, tablets, etc.), you might notice
some elements change in color or style when you scroll. ]is is because the
:hover style ^res on scroll. ]is is a problem. ]e solution is to use the hover
media query. According to Mozilla Developer Network:
]e hover CSS media feature can be used to test whether the user’s
primary input mechanism can hover over elements.
With this, we prevent :hover styles from ^ring for mobile and tablet users. At
the time of writing, this feature is supported in all major browsers. ]e good
thing is that when you active device mode in Chrome, it will be considered a
touch screen, so you can test the hover media query there.
]ere is no direct way to display potential errors in CSS. However, some clever
folks have devised workarounds that enable us to debug incorrect usage of
HTML and CSS. Let’s explore some of them.
Let’s say you’ve built a design system with your team, and you want to lint
errors related to incorrect usage of a component in the design system. In his
methodology named Inverted Triangle CSS (ITCSS), Harry Roberts uses the
following classes to create warnings about incorrect usage of CSS classes.
<div class="o-layout">
<div class="o-layout__item"></div>
<div class="o-layout__item"></div>
<div class="o-layout__item"></div>
</div>
<div>
<div class="o-layout__item"></div>
</div>
]e element with the .o-layout__item class shouldn’t live on its own like
this. We can debug this very easily:
.o-layout__item {
/* Show a warning outline by default. */
outline: solid 5px yellow;
}
.o-layout .o-layout__item {
/* Remove the outline when item is in .o-layout. */
outline: none;
}
Generally speaking, width and height attributes are not recommended for
any HTML elements, except img .
:not(img):not(object):not(embed):not(svg):not(canvas)[width],
:not(img):not(object):not(embed):not(svg):not(canvas)[height] {
outline: solid 5px red;
}
Going further, you can use Gaël Poupard’s browser extension, a11y.css, which
shows di`erent advice, warnings, and errors.
]e ^rst person that encouraged me from the community is Mr. John Allsopp.
He invited me to talk at Web Directions conference about the book topic and
was one of the ^rst supporters. ]ank you very much!
Finally, I would like to thank Bram Van Damme, who reviewed the very
^rst draft of the book. He highlighted some important things that I should
improve. ]ank you, Bram!