Summary
In a production photo gallery, we implemented a CSS Grid with a 4-column template to display portfolio thumbnails. Some entries required grouping multiple images semantically. To achieve this, we applied CSS subgrid to the parent containers, intending for the child images to participate in the parent’s horizontal track layout.
The issue was a layout misalignment where the subgrid’s internal items, when not an exact multiple of the parent’s column count (4), caused a negative grid gap calculation or ghost space in subsequent rows. This resulted in broken visual alignment between the subgrid item and its adjacent siblings in the next row.
Root Cause
The root cause is the behavior of implicit grid tracks in CSS Grid combined with how subgrid propagates tracks.
- Implicit Grid Behavior: When a grid item spans multiple columns, the browser creates implicit rows to hold any content that does not fit within the spanned area.
- Subgrid Propagation: The subgrid container (
portfolio-piece--nested-items) inherits the column tracks from the parent grid. However, it defines its own row tracks to hold the child images. - The Gap: When the subgrid contains 9 items (as in the example), the first 4 items fill the first row (spanning the parent). The remaining 5 items force the creation of a second row inside the subgrid. However, because the subgrid column tracks are defined by the parent and the subgrid is strictly spanning
span 4, the browser attempts to align the internal tracks of the subgrid to the parent’s 4-column structure. When the item count isn’t a multiple of 4, the row sizing on the internal implicit track becomes erratic—often collapsing or expanding based on content, which pushes the actual parent grid items out of alignment, creating visual gaps.
Why This Happens in Real Systems
This scenario occurs frequently in content management systems (CMS) where data is dynamic and schema-less.
- Variable Content: In a portfolio system, an “entry” is a data object. One entry might have 1 image; another might have 5. Hardcoding strict row definitions is impossible.
- Semantic HTML Requirements: Developers often group items in a single
div(the subgrid container) to satisfy ARIA accessibility rules (e.g., a carousel or gallery group). - Subgrid Limitations: While subgrid solves horizontal alignment, it does not automatically solve vertical rhythm mismatches. If the content inside the subgrid is taller or shorter than expected, it disrupts the grid-auto-rows flow of the parent.
Real-World Impact
- Visual Disintegration: The primary impact is UI breakage. The grid looks broken and unprofessional. Items that should align horizontally appear staggered or with excessive whitespace.
- Responsive Failures: On smaller screens, where the grid might collapse to 2 or 1 column, the mismatch in implicit tracks causes even larger gaps, potentially pushing content off-screen or creating layout shifts (CLS – Cumulative Layout Shift).
- Maintenance Burden: The fix often involves writing media queries or complex JavaScript to calculate the remaining items, adding bloat to the codebase.
Example or Code
The following HTML and CSS illustrate the issue. Notice the grid-column: span 4 on the subgrid container, but the internal content creates an implicit row that breaks the visual flow.
.grid {
display: grid;
/* 4-column layout */
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 1.5rem;
max-width: 69rem;
margin: 0 auto;
}
.portfolio-piece {
display: grid;
}
.portfolio-piece--nested-items {
/* Inherit parent columns, but define own rows */
grid-template-columns: subgrid;
gap: 1.5rem;
/* Span the entire width of the parent */
grid-column: span 4;
/* CRITICAL: This creates the implicit row issue */
grid-auto-rows: min-content;
}
.portfolio-piece img {
width: 100%;
display: block;
}
How Senior Engineers Fix It
Senior engineers approach this by removing the reliance on implicit grid behavior for structural layout.
-
Flatten the Grid (Preferred Solution):
Instead of nesting a subgrid that attempts to manage rows, use direct children of the parent grid.- Use a wrapper
divfor semantic grouping only if it doesn’t participate in the grid flow. - If grouping is necessary, create a utility class that spreads the children into the parent grid using
display: contents. - Fix: Remove
grid-column: span 4from the container and apply it to the images individually or usedisplay: contentson the container so the images become grid items of the parent.
- Use a wrapper
-
Explicit Row Sizing:
Ifdisplay: contentsis not semantically valid for the project (due to accessibility requirements), you must explicitly define the subgrid rows to match the parent’s expectation.- Calculate the number of rows needed.
- Use
grid-template-rows: repeat(auto-fill, auto);carefully, but understand this often requires a polyfill or strictly controlled content height.
-
The Robust Fix (No JS):
Wrap the images in the subgrid container, but ensure the subgrid container does not define its own internal row tracks that conflict with the parent..portfolio-piece--nested-items { display: contents; /* Removes the box, children become grid items */ } /* Note: 'display: contents' removes the container from the accessibility tree in some browsers. Use role="group" on the children if this is a concern. */
Why Juniors Miss It
- Conceptual Gap: Juniors often view the DOM as a strict visual hierarchy. They don’t realize that
display: subgridordisplay: contentschanges which element acts as the “grid container.” - Focus on Syntax over Logic: They often focus on getting the CSS syntax (
subgrid,span) working but fail to analyze the track sizing algorithm. They see that items align horizontally (whichspan 4guarantees) but miss the vertical spacing issues caused by implicit rows. - Misunderstanding
span: Junior engineers assumegrid-column: span 4fixes vertical positioning. It does not; it only affects the column axis. The row axis is determined by the parent’s row definitions or implicit row sizing, which is where the gap occurs. - Over-Nesting: The tendency to wrap everything in a
divfor styling purposes leads to deeply nested grids that are difficult to debug. A senior engineer looks for the flattest possible DOM structure.