Search code examples
htmlcssgoogle-chromefirefoxcss-multicolumn-layout

Why does overflow:hidden break multiple column rendering in Firefox but not in Chrome?


I recently stumbled across a strange behavior in Firefox with lists in an columns:2 div.

If the list has overflow:hidden set Firefox doesn't render in 2 columns anymore. rendering in Chrome it is as expected in 2 Columns (with and without overflow).

Here's a minimal example:

$('button').on('click', (e) => {
  $('ul').toggleClass('overflow')
})
div {
  columns:2;
}

ul.overflow {
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>toggle overflow</button>
<div>
    <ul>
        <li>Lorem ipsum dolor.</li>
        <li>Consequuntur, ab nostrum.</li>
        <li>Quibusdam, iure, fuga.</li>
        <li>Suscipit quisquam, vel.</li>
        <li>Assumenda architecto, adipisci!</li>
        <li>Molestias, nostrum ratione.</li>
        <li>Quaerat, eveniet, in.</li>
        <li>Illum, debitis, dicta.</li>
        <li>Tempore, placeat, ea.</li>
        <li>Amet dignissimos, maiores.</li>
        <li>Odio, eos, ullam.</li>
        <li>Modi libero, quis!</li>
        <li>Aliquid, commodi, voluptates.</li>
        <li>Aperiam, magni, vel.</li>
        <li>Vitae, minima dolorum!</li>
        <li>Quidem, corporis, dolorum.</li>
        <li>Autem, minima, sit.</li>
        <li>Adipisci, odio, numquam.</li>
        <li>At, dicta, hic!</li>
        <li>Odit, blanditiis voluptate.</li>
    </ul>
</div>

On CodePen: https://codepen.io/BugHunter2k/pen/rNaVpGG

What rendering is right? Is there something I can add so Firefox renders 2 columns even with overflow:hidden


Solution

  • While Thierry's answer includes a solution to fix one problem, it also fixes the actual problem you're facing (inadvertently, I believe), and problematically lays blame with the wrong browser. Let's break things down:

    First, this is actually working correctly in Firefox, and incorrectly in Chrome.

    You have a div which is acting as a multi-column container due to columns: 2, which is shorthand for:

    div {
        column-count: 2;
        column-width: auto;
    }
    

    This multi-column container establishes a new block formatting context, so any properties on descendant elements will be applied relative to the div rather than the page in this example.

    One thing to keep in mind is that multi-column layouts are a bit flexible (I don't mean in relation to flexbox layout, either) ...they'll try to adhere to the values you give, but if they can fit in fewer columns, or if they have to take up more columns to fit your markup, they are likely to do that. You'll see an example of this later on in my answer.

    Within the div you have an unordered list, which normally displays its children in a vertical list, but because of columns: 2 from earlier, your <ul> is split automatically to fit into two columns as best it can. Because there's no height or width properties declared anywhere (in general or as column- properties), it just splits the contents 50-50. The misalignment you see between the two halves of the list is because <ul> by default has margin-top property which is pushing the first half of list-items down 16px (usually the size of margins on <ul>s).

    The reason that applying columns: 2 in ul {} instead of div {} solves your problem is because you're now splitting the <ul> itself into two columns rather than trying to squeeze a normal <ul> into a multi-column container however it can fit; the browser knows to apply the same formatting to the beginning of each column this way.

    Now, a <div> is, by default, as wide as its container (because of its default display:block; property) and as tall as its content. If you were to add more items to the <ul>, the two sets of list-items would grow in height (if you have a background-color set for the div, you can see that more easily).

    Referring back to that flexibility I mentioned, if you were to cap the height of your div at, say, 150px, you'd see that the number of columns increases and overflows the div by default:

    div {
        columns:2;
        column-rule: thin solid red; /* this value added just to illustrate the split between columns */
        height: 150px;
        background: grey; /* this value added for ease of visual comprehension */
    }
    
    ul.overflow {
        overflow: hidden;
    }
    <div>
        <ul>
            <li>Lorem ipsum dolor.</li>
            <li>Consequuntur, ab nostrum.</li>
            <li>Quibusdam, iure, fuga.</li>
            <li>Suscipit quisquam, vel.</li>
            <li>Assumenda architecto, adipisci!</li>
            <li>Molestias, nostrum ratione.</li>
            <li>Quaerat, eveniet, in.</li>
            <li>Illum, debitis, dicta.</li>
            <li>Tempore, placeat, ea.</li>
            <li>Amet dignissimos, maiores.</li>
            <li>Odio, eos, ullam.</li>
            <li>Modi libero, quis!</li>
            <li>Aliquid, commodi, voluptates.</li>
            <li>Aperiam, magni, vel.</li>
            <li>Vitae, minima dolorum!</li>
            <li>Quidem, corporis, dolorum.</li>
            <li>Autem, minima, sit.</li>
            <li>Adipisci, odio, numquam.</li>
            <li>At, dicta, hic!</li>
            <li>Odit, blanditiis voluptate.</li>
        </ul>
    </div>

    As you can see, the div is as wide as the container (no matter how big your container is) and the columns are split into three, because that's required to fit the amount of list-items you have. It does this by overflowing the children element as necessary to try and keep the columns an equal height. The secret to the behavior you see when toggling the button is what overflow: hidden; does.

    When you apply overflow: hidden; to an element, it also establishes a new block formatting context. Creating a new block formatting context basically says "reset my layout computation from scratch here; don't factor in what the containing block was doing". In your case, it's saying "ignore the CSS column layout in the previous block formatting context and just display like a <ul> normally would". Chrome isn't doing this, but it should be. It could be a case of the implementation being wrong/incomplete, or it could be a case of Chrome devs making a decision based on what they think most people would want. Both situations are common with that vendor.

    Second, Thierry's suggestion to fix the uneven vertical alignment of the two parts of the <ul> was to apply the column properties to the <ul>. That particular goal could also be accomplished by removing the margin-top property/setting it to 0, but this method of solving that goal also does the following:

    • Applies proper multi-column layout settings to the <ul> (as I mentioned before, now the browser knows to handle the list and the list-items within the context of a multi-col layout rather than just trying to squeeze in a list to a multi-column container).
    • Setting the columns property on the <ul> itself means that, when a new block formatting context is created due to overflow: hidden;, nothing changes with regard to the <ul> ignoring stuff from the parent <div> and its block formatting context. In other words, the multi-column layout is applied within the new block formatting context created by overflow: hidden;.

    This new behavior is ultimately what you want, if you are only trying to split up an unordered list into multiple columns. If you want multiple types of elements (or just multiple elements) considered for the multi-column layout, keeping it on the parent <div> would be necessary.