Exploring CSS where it doesn’t make sense

exploring-css-where-it-doesn’t-make-sense

Ever felt like CSS is playing tricks on you? Despite its outward simplicity, CSS has layers of complexity that can even confuse the best of developers. Beginners often jump into using CSS without fully understanding the why and how behind its behavior. While it’s not a bad practice to jump right in and start experimenting, the nature of CSS will eventually lead to confusion and frustration.

In this article, I want to explore some of the generally more unknown and overlooked aspects of CSS. Throughout the article, I’ll share common practices to bring your CSS under control and avoid falling down the “WTF, how did that happen?” rabbit hole.

I hope you’ll find this article helpful no matter your level of expertise.

Let’s get started! 🚀

css

Table of Contents

  • Understanding the core of CSS

    • Formatting context
    • Layout Modes
  • CSS Gotchas

    • Margin collapse
    • Stacking context
    • Specificity
    • Floats
  • Conclusion
  • Resources

Understanding the core of CSS

Before we dive into more specific CSS traps, let’s have a look at core concepts to build a fundamental intuition on how CSS works. Having the right mental model helps a lot in predicting how CSS will behave in different scenarios.

CSS started as a simple language to style documents. You could write paragraphs, headings, and lists, and then style them with CSS. But as the web evolved, so did CSS. With this evolution, we had to adapt to more and more requirements of the web. Here are a few examples:

  • Responsive design: Making sure your website looks good on all screen sizes, browsers, and devices.
  • Reliability: A small syntax error should not crash your entire website.
  • Customization: Users should be able to customize your website to their liking. Example: dark mode, font size, etc.
  • Accessibility: Making sure your website is accessible to everyone, including people with disabilities. Adapting to screen readers, keyboard navigation, and more.
  • Reusability: Making sure that code is easily reusable as you have common components like buttons, inputs, etc.
  • Looks: While considering all the above, your website should still look good.
  • Developer experience: Making sure that developers can write and maintain CSS easily and quickly.

It’s impressive how CSS has evolved to meet these requirements while still being simple to write. Most of the time! 😅

As you can imagine a lot is going on under the hood. There are so many rules that decide what to fall back to when a CSS feature is used in a way that was not intended. These fallbacks are decided based on the needs of the web as a whole, and not just your website. And this is where the confusion starts.

There are so many topics I could cover, but I’ll only focus on a few and leave the rest up to you. At the end, after you have read this article you can have a look at the resources section to learn more.

Formatting context

The first core concept I want to talk about is the formatting context. If you have worked with CSS for a while, you should already have an intuition about how this works. And even if you do you’ll benefit from knowing the details behind it.

On your website, you have a lot of elements. And each element “behaves” in the context of its formatting context. The two main formatting contexts are:

  • Block formatting context: In this context, the element will take up the entire width of its parent and will start on a new line. These elements are called block-level elements. Examples are div, p, h1, section, etc.
  • Inline formatting context: In this context, the element will only take up as much width as it needs and will continue on the same line if there is enough space. These elements are called inline-level elements. Examples are span, a, strong, em, etc. You can’t change the width or height of inline-level elements nor can you add top and bottom margins.

The main way of defining how an element behaves is the display property. The newer syntax is display: . The outer value can be block or inline defining the formatting context of the element. The inner value is how children of the element will behave. Example: display: inline flex; (Older version for browser support: display: inline-flex;). This will place the element in the same line as the last element if there is enough space and the children will behave like flex items. Here are a few more:

/* Current */ /* New Syntax */
display: block; /* display: block flow; */
display: inline; /* display: inline flow; */
display: inline-block; /* display: inline flow-root; */
display: flex; /* display: block flex; */
display: inline-flex; /* display: inline flex; */
display: grid; /* display: block grid; */
display: inline-grid; /* display: inline grid; */
display: flow-root; /* display: block flow-root; */

This brings us to the next core concept…

Layout Modes

The second part of the display property is the layout mode. This is how the children of the element will behave. The default and most intuitive layout mode is flow (Normal flow). Children in a normal flow layout will simply stack on top or next to each other based on the formatting context. This is happening by default when you start adding elements to your website. Besides the normal flow, there are a few more common layout modes:

  • Flex layout: This is a one-dimensional layout. You can align items horizontally or vertically. This is great for navigation bars, sidebars, and more. This layout has a ton of features and gotchas that deserve an article on their own. If you want to learn more check out the resources section.
  • Grid layout: This is a two-dimensional layout. You can align items in rows and columns. This is great for complex layouts like a dashboard, a gallery, and more. This layout also has a ton of features and gotchas that deserve an article on their own.
  • Positioned layout: This is a layout where you can position elements anywhere on the page. This is great for tooltips, modals, and more. While we don’t use the display property to define this layout, it’s still part of the layout modes and we’ll cover its gotchas later.
  • Float layout: This is a layout where you can float elements to the left or right of inline-level elements. This is great for wrapping text around images. This layout is not used as much anymore and has a lot of gotchas. We’ll cover this one too.

You probably asked yourself what flow-root is. We will get to that when we talk about CSS Gotchas next.

CSS Gotchas

Margin collapse

Margin collapse is when the top and bottom margins of two elements collapse into one margin. It was originally intended to make the vertical spacing between your elements more consistent. There are a TON of rules that decide whether margins collapse or not. No one wants to remember all of them and many don’t even know about margin collapse. So it’s important to know how to avoid it and how to fix it when it happens.

collapse

Margins collapse happens in the following scenarios (these are not all of the rules):

  • The elements are adjacent.
  • No padding, border, or clearance separates the two elements.
  • The elements are in the same formatting context. A new formatting context is created when:
    • You have a float, flex item, grid item, or absolutely positioned element (with absolute or fixed).
    • The element has a display of inline-block, flow-root, flex, grid, inline-flex, inline-grid, and a few more.
    • The element has an overflow other than visible and clip (hidden, auto, scroll, or overlay).
  • If one of the elements is empty or its height is zero.

As you can see you don’t want to ever see this list again. So let me just give you an interactive example of what margin collapse looks like and how to avoid it.

Generally, you shouldn’t use margins for everything. When you have a card, section, button, header, or any other element where you always want to create space around the content but inside the element use padding. Margins are mainly for two things:

  1. Horizontal space between elements. Like icons in text for example.
  2. To create vertical space between sections and paragraphs. Here only use margin-top to avoid margin collapsing altogether.

A tip for avoiding margin collapse for content sections is to use a “Lobotomized Owl” selector. With it, you can add margin-top to all children except the first one essentially putting a margin between all children just like the gap property of flex.

/* Add margin-top to all children except the first one */
.section > * + * {
  margin-top: 1rem;
}

If you use Tailwind CSS you can use the space-y class to achieve the same effect.

 class="space-y-4">
  

...

...

...

Another tip is to always check your elements with the developer tools to see where your margins end up. And if they collapse and you don’t want them to, now you know how to fix it (for example by creating a new formatting context with display: flow-root;). Often you accidentally create a new formatting context and your paddings look like they got bigger. Now you know why.

Stacking context

Visualize a series of nested boxes, where each box can contain several smaller boxes inside. Each of these smaller boxes can, in turn, contain even more boxes, creating a complex, multi-layered structure. This analogy shows the concept of stacking contexts in CSS.

In this metaphor, each box represents an element with its own stacking context. The z-index property determines the stacking order of elements within their particular box. However, it’s important to realize that z-index values only apply within the same box or stacking context. This means a smaller box nested inside cannot be placed above its containing box, regardless of its z-index value. This is where the gotcha comes in.

Many assume z-index is a universal scale, where higher values always appear on top of lower values across the entire page, similar to expecting a small, inner box to sit on top all outer boxes if it’s marked with a higher number. The reality is that z-index only organizes elements within their immediate box or stacking context. An element with a z-index of 1000 inside a nested box won’t necessarily be above an element with a z-index of 1 in another, outer box.

This is a common source of confusion, especially when working with complex layouts or nested components. I’m sure you’ve encountered a situation before where you put the z-index at 9999999 and it still didn’t work. So let’s see when these boxes (stacking contexts) are created.

Oh no, here we go again 💀:

  • The element creates a stacking context by default.
  • An element with an opacity value less than 1.
  • An element with one of these properties: transform, filter, backdrop-filter, perspective, clip-path, mask.
  • An element with a position value absolute or relative and z-index value other than auto.
  • An element with a position value of fixed.
  • An element with a mix-blend-mode.
  • An element with an isolation value of isolate.
  • A flex or grid item with a z-index value other than auto.
  • An element with a will-change value of any of the above properties.
  • And a few more…

flashback

Oof, that’s a lot of ways to create a stacking context. So how can we avoid this? Well, you can’t really avoid it but you can decrease the chance of fighting with z-index in the following ways:

  • Avoid using position: absolute; just to center elements.
  • Don’t use z-index on non-positioned elements. (Elements without a position).
  • Use consistent z-index values. For example, use only 10, 20, 30, 40, 50, etc.

Lastly, keep stacking context in mind when working with properties like opacity, transform, filter, and mix-blend-mode.

Specificity

In CSS, specificity is the set of rules that determines which style declarations are applied to an element when more than one rule could apply. However, complexity in specificity can lead to a CSS labyrinth, making it challenging to predict and control which styles will win. To understand specificity, consider an example where we have an HTML element with both a class and an ID selector applied to it:

#product-highlight {
  background-color: yellow;
}

.product {
  background-color: blue;
}

Despite both styles applying to the same element, the background color will be yellow because ID selectors have a higher specificity than class selectors. The same goes for complex selectors like .product > .highlight or .product.highlight. The former has a higher specificity because it’s more specific. Most of the time you’ll be fine but as your project grows you will run into specificity issues more often and the solution is not always simple. To avoid these issues, it’s best to keep specificity as low as possible.

To solve this while keeping CSS readability high you could adopt naming conventions like BEM (Block Element Modifier). BEM aims to make CSS more maintainable by reducing specificity conflicts through a flat structure of class names. This involves naming your CSS classes like this: .block__element--modifier.

  • Block: Standalone entity that is meaningful on its own. (Like a card, button, or header)
  • Element: A part of a block that has no standalone meaning and is semantically tied to its block. (Like a title, subtitle, or button text)
  • Modifier: A flag on a block or element. Used to change appearance or behavior. (Like a button with a primary color or a card with a shadow)
 class="card card--highlight">
  

class="card__title">Product Name

class="card__description">Product Description

.card {
  ...;
}

.card--highlight {
  ...;
}

.card__title {
  ...;
}
.card__description {
  ...;
}

By using only class selectors, all selectors have the same specificity level and you won’t run into specificity issues. Another issue I’ve often seen is with using SCSS. People (especially beginners) often nest their selectors just like their HTML. This wouldn’t only lead to a specificity nightmare but also to big CSS files. So avoid nesting your selectors if you don’t benefit from it.

/* I'm sorry but this just hurts to look at */
section {
  .card {
    .title {
      i {
        font-size: 13px;
      }
    }
  }
}

burn

Floats

For the last gotcha, I want to talk about floats. Floats were mainly used to wrap text around images. They were also used to create complex layouts before Flexbox and Grid became a thing. But nowadays you should avoid using floats as much as possible. They have a lot of gotchas and are often misunderstood by newer developers.

Floats are often used to just put stuff on the left or right. This might work but will lead to headaches later on. Here is what happens when you use floats:

  • They are removed from the normal flow.
  • They are placed to the left or right of only inline-level elements.
  • They become block-level elements.
  • They create a new formatting context. (Meaning they disallow margin collapse)
  • They have their own stacking rules (between non-positioned elements and positioned elements)

As you can see, they do a bit more than just putting stuff on the left or right. If you don’t intend to absolutely position the element in a way where only inline-level elements are affected (like text) don’t use floats.

Instead, use Flexbox or Grid. They are much more powerful and easier to use. For aligning inline-level elements use text-align instead.

Conclusion

I’d love to continue listing more gotchas but I want to encourage you to explore the MDN Web Docs on your own. They are extremely valuable and when you understand more and more of the underlying concepts of CSS you will have fewer and fewer issues writing it. I hope this article was helpful and you learned something new 😊. If you have any questions or feedback, feel free to leave a comment. I’d love to hear from you. Otherwise, feel free to share this article and give it a like. Thank you for reading and happy coding! 🚀

cool

Resources

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
what-should-a-project-timeline-include?

What should a project timeline include?

Next Post
15-b2b-marketing-metrics-that-matter-to-ceos

15 B2B Marketing Metrics That Matter to CEOs

Related Posts