Stop CSS columns from splitting an element: break-inside: avoid
CSS multi-column layout (columns: 2) is a quick way to flow a list of cards or links into columns without a JavaScript masonry library. By default, though, the browser fills the columns like text: when a column runs out of space mid-element, the element simply continues in the next column, borders, padding and all.
The examples below were rendered in headless Chromium 153, Firefox 155 and WebKit 26.6 using the official Playwright Docker image. To detect splitting, the test counted each item's layout fragments (getClientRects().length): an item cut across two columns has two.
The problem
<div class="columns">
<div>Item 1</div>
<div>Item 2, which is slightly longer</div>
<div>Item 3, which is also longer and jumps to the next column</div>
<div>Item 4</div>
<div>Item 5</div>
<div>Item 6</div>
</div>.columns {
columns: 2;
column-gap: 16px;
width: 400px;
}
.columns > * {
border: 2px solid #0ea5e9;
border-radius: 6px;
padding: 8px;
margin: 0 0 8px;
}
Item 3 starts at the bottom of the first column and finishes at the top of the second. Its bottom border is missing in column one and its top border is missing in column two. All three engines split it the same way (#3 in 2 pieces).
The fix
.columns > * {
break-inside: avoid;
}
Item 3 now stays in one piece and the columns rebalance around it. The fragment count was 1 for every item in Chromium, Firefox and WebKit. break-inside: avoid inside multi-column layout is supported in Chrome 50+, Firefox 65+ and Safari 10+ according to MDN's compatibility data, so you don't need a fallback today.
What about page-break-inside and the other tricks?
The original version of this post recommended page-break-inside: avoid. Every variant tested kept the items whole in all three engines:
none split items: #3 in 2 pieces
avoid split items: none ← break-inside: avoid
legacy split items: none ← page-break-inside: avoid
avoid-column split items: none ← break-inside: avoid-column
inline-block split items: none ← display: inline-block; width: 100%page-break-insideis the legacy property from paged media. The CSS Fragmentation spec requires browsers to alias it tobreak-inside, which is why it still works. Usebreak-insidein new code.break-inside: avoid-columnavoids breaks only between columns, whileavoidalso covers page breaks when printing.avoidis what you usually want. (avoid-columnarrived later in Firefox, in version 92.)display: inline-block; width: 100%is an old workaround from before browsers supportedbreak-insidein columns. It works because inline-level boxes aren't fragmented, but it changes layout behaviour (whitespace gaps, vertical alignment, margins), so there's no reason to use it any more.
The one case it can't fix
break-inside: avoid is a request, not a guarantee. If an element is taller than a column, it has to break somewhere. With a fixed-height container (height: 160px; column-fill: auto) and an item 220 px tall:
chromium: item taller than the column, with break-inside: avoid → 2 pieces
firefox: item taller than the column, with break-inside: avoid → 2 pieces
webkit: item taller than the column, with break-inside: avoid → 2 piecesThat only happens when the container has a fixed height. With the default automatic height, the columns grow to fit the tallest unbreakable item instead. If you need items that never split and also a fixed height, multi-column layout is the wrong tool. Use CSS grid or flexbox with real rows instead.
Two related tips
- Columns fill top to bottom, then left to right, so item 2 sits below item 1, not beside it. If the reading order should go across, use grid instead.
break-inside: avoidalso works on elements inside a printed page (tables, figures, code blocks). Add it to your print stylesheet to keep a figure and its caption together.