How CSS grid-auto-flow: dense Fills Grid Holes

CSS Grid provides a powerful auto-placement algorithm to position elements without requiring explicit coordinates for every item. When grid items have varying sizes or span multiple tracks, the default layout often creates awkward, empty spaces. The grid-auto-flow: dense property solves this issue by instructing the browser to backtrack through the grid and pack smaller elements into previously bypassed holes, producing a compact and gap-free layout.

The Problem: Default Auto-Placement

By default, CSS Grid uses grid-auto-flow: row (or grid-auto-flow: column). Under this behavior, the browser processes items strictly in the order they appear in the Document Object Model (DOM).

When the browser encounters an item that spans multiple columns or rows and cannot fit into the current available space, it leaves that space empty and moves the large item to the next available track. Crucially, the standard algorithm only moves in a forward direction; it will never look backward to fill the gap it just left, even if later items in the source order are small enough to fit. This results in unintended empty "holes" across the grid.

What is grid-auto-flow: dense?

The dense keyword modifies the placement algorithm by enabling a "dense" packing mode. Instead of strictly preserving DOM sequence along the grid tracks, grid-auto-flow: dense allows the browser to reorder items visually to optimize space utilization.

You can declare it on a grid container using either of the following syntaxes:

.grid-container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: dense; /* Defaults to row dense */
}

/* Or explicitly set the flow direction alongside dense */
.grid-container {
  grid-auto-flow: row dense;
  /* or */
  grid-auto-flow: column dense;
}

How It Fills Empty Layout Holes

When dense is enabled, the browser handles placement through a dynamic search process:

  1. Space Allocation: As the browser encounters large items (e.g., an element set to grid-column: span 2), it attempts to place them. If the current row does not have enough remaining space, the item shifts to the next row, creating a gap behind it.
  2. Backtracking: When subsequent items are processed, the layout engine does not automatically append them after the most recently placed item. Instead, it scans the entire grid from the start to identify the first open slot where the current item can physically fit.
  3. Slot Filling: If a subsequent item has a small footprint (such as a standard 1x1 cell item), and a preceding gap can accommodate its dimensions, the browser places the item into that earlier gap.

This mechanism ensures that empty cells created by oversized items are populated by any later, smaller elements found down the line in the HTML markup.

Practical Use Cases

  • Image Galleries: Dynamic photo grids with a mix of portrait, landscape, and panoramic images can achieve a balanced, masonry-style look without empty cells.
  • Dashboard Widgets: Modular dashboards containing cards of varying dimensions (1x1, 2x1, 2x2) can pack tightly without manual positioning.
  • Product Listings: Highlighting specific "featured" products with larger spans while keeping the overall grid compact.

Accessibility and Usability Considerations

While grid-auto-flow: dense creates visually seamless layouts, it alters the visual presentation relative to the DOM source order.

Because keyboard navigation (such as the Tab key) and screen readers follow the DOM order rather than the visual layout, navigating a dense grid can cause the focus ring to jump erratically across the screen. For this reason, grid-auto-flow: dense should primarily be used for non-sequential content where reading order and tab order are not critical to the user experience.