Grid Layout Centering with Responsive Breakpoints
Grid Layout Centering with Responsive Breakpoints
The Challenge: Centering the Last Item in Odd-Numbered Grids
We needed to create a responsive card grid layout where:
- Cards would display in multiple columns on larger screens
- When the grid collapses to 2 columns, if there are exactly 3 items, the third item should be centered across both columns
- The layout should be responsive and maintain proper spacing at all screen sizes
The issue occurred in the
IconHeaderMessageCardGrid
component, which was used in the Section__VibeCodeWithUs
section of our site.Initial Complex Approach (Not Working)
Our first approach used container queries and complex calculations to determine grid layout:
astro
<!-- IconHeaderMessageCardGrid.astro -->
<div class="icon-header-message-card-grid">
{cards.map(card => (
<div class="card-container">
<IconHeaderMessageCard
title={card.title}
description={card.description}
badge={card.badge}
to_path={card.to_path}
verified={card.verified}
>
{card.icon && (
<span set:html={card.icon} />
)}
</IconHeaderMessageCard>
</div>
))}
</div>
<style>
.card-container {
height: 100%;
width: 1fr;
display: flex;
justify-content: center;
}
.icon-header-message-card-grid {
display: grid;
max-width: 76vw;
gap: clamp(1rem, 4vw, 2.5rem);
margin-inline: auto;
margin-block: 3rem;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
container-type: inline-size;
@container (width >= calc(260px * 2 + 1rem)) {
.card-container {
grid-column: span 2;
}
}
/* Handle 3 items in a 2-column layout - center the third item */
@container (width < calc(280px * 3 + 2rem)) {
&:has(.card-container:nth-child(3):last-child) {
.card-container:last-child {
grid-column: 1 / -1; /* Span all columns */
max-width: 340px;
margin-inline: auto;
}
}
}
/* Additional complex container queries for various screen sizes */
/* ... */
}
</style>
Despite our efforts with complex container queries, the cards were collapsing into a single column, and the centering of the third item wasn't working as expected.
The "Aha!" Moment: Simplifying the Approach
The breakthrough came when we realized that:
- Container queries might be causing issues in this context
- The calculations were overly complex and difficult to debug
- Traditional media queries would be more reliable for this layout
The key insight was that sometimes a simpler approach with standard responsive techniques works more reliably, especially when dealing with grid layouts.
The Solution: Simplified Media Queries
We completely rewrote the grid layout using standard media queries:
astro
<!-- IconHeaderMessageCardGrid.astro (updated) -->
<style>
.card-container {
height: 100%;
display: flex;
justify-content: center;
/* Ensure the container takes up the full grid cell width */
width: 100%;
min-width: 0; /* Prevent overflow in grid cells */
}
.icon-header-message-card-grid {
display: grid;
max-width: 1200px;
width: 95%;
gap: 1.5rem;
margin: 2rem auto;
grid-template-columns: repeat(3, 1fr);
}
/* Responsive adjustments */
@media (max-width: 1024px) {
.icon-header-message-card-grid {
grid-template-columns: repeat(2, 1fr);
}
/* Center the third item when there are exactly 3 items */
.icon-header-message-card-grid:has(.card-container:nth-child(3):last-child) .card-container:last-child {
grid-column: 1 / -1;
justify-self: center;
max-width: 340px;
}
}
@media (max-width: 640px) {
.icon-header-message-card-grid {
grid-template-columns: 1fr;
}
}
</style>
We also updated the card component to work better with the grid:
astro
<!-- IconHeaderMessageCard.astro (updated) -->
<style>
.icon-header-message-card {
position: relative; /* Needed for badge positioning */
background: var(--clr-card-bg, #181a20);
border-radius: 1rem;
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
padding: 2rem 1.5rem 1.5rem 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
max-width: 340px; /* Prevents a single card from stretching too wide */
width: 100%; /* Fill the container */
transition: box-shadow 0.2s, border 0.25s;
border: 1.5px solid rgba(255,255,255,0.06); /* default border for smooth transition */
}
</style>
Key Takeaways
- Simplicity over complexity: Standard media queries were more reliable than complex container queries for this layout.
- Fixed grid template: Using
grid-template-columns: repeat(3, 1fr)
with clear breakpoints at common screen sizes (1024px and 640px) provided more predictable results. - Targeted styling for special cases: The
:has()
selector withgrid-column: 1 / -1
andjustify-self: center
effectively centered the third item in a 3-item grid. - Container setup: Setting
width: 100%
andmin-width: 0
on the card container prevented overflow issues and ensured proper sizing.
This solution is more maintainable because it's easier to understand, uses standard responsive techniques, and has fewer moving parts that could break in the future.