Centering content inside a box should be simple. With display: grid and place-items: center, it is a one-line solution. In this tutorial you will learn what both properties do, when to use them, and how to build a responsive gallery of “shape cards” where each card perfectly centers its content in both directions.
Why display: grid and place-items: center Matters
Developers spend a lot of time aligning things: loaders, avatars, buttons, cards, and empty states. CSS Grid gives you two-dimensional layout control, and the shorthand place-items sets align-items and justify-items at the same time. That means centering a child block vertically and horizontally without extra wrappers, flex hacks, or arbitrary margins. The result is cleaner markup, predictable alignment, and fewer layout bugs during responsive changes.
Prerequisites
You do not need advanced Grid experience for this tutorial. A basic understanding of HTML and CSS will help you follow along. Custom properties will make theme tweaks painless, and pseudo-elements are handy if you want to extend the demo later.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
Here is the complete HTML for a small shape gallery. Each card contains a visual shape and a label. The section uses semantic landmarks, and the decorative shapes are marked as aria-hidden to avoid noise for assistive tech.
<!-- HTML -->
<section class="gallery" aria-label="Centered shape cards">
<article class="card">
<div class="shape circle" aria-hidden="true"></div>
<h3 class="title">Circle</h3>
</article>
<article class="card">
<div class="shape square" aria-hidden="true"></div>
<h3 class="title">Square</h3>
</article>
<article class="card">
<div class="shape triangle-up" aria-hidden="true"></div>
<h3 class="title">Triangle Up</h3>
</article>
<article class="card">
<div class="shape circle" aria-hidden="true"></div>
<h3 class="title">Circle</h3>
</article>
<article class="card">
<div class="shape square" aria-hidden="true"></div>
<h3 class="title">Square</h3>
</article>
<article class="card">
<div class="shape triangle-up" aria-hidden="true"></div>
<h3 class="title">Triangle Up</h3>
</article>
</section>
Step 2: The Basic CSS & Styling
This base layer sets colors, spacing, and the grid that lays out the card collection. The gallery uses responsive columns with auto-fit and minmax, so the number of cards per row adapts to the viewport.
/* CSS */
/* CSS */
:root {
--bg: #0f172a; /* slate-900 */
--panel: #111827; /* gray-900 */
--panel-border: #1f2937; /* gray-800 */
--text: #e5e7eb; /* gray-200 */
--muted: #9ca3af; /* gray-400 */
--accent: #60a5fa; /* blue-400 */
--size: 72px;
--radius: 14px;
--gap: 1rem;
}
*,
*::before,
*::after { box-sizing: border-box; }
html, body {
height: 100%;
}
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
background: radial-gradient(1200px 800px at 20% 0%, #111827 0%, #0b1022 60%, #030712 100%);
color: var(--text);
line-height: 1.5;
display: grid;
place-items: start center;
padding: 3rem 1rem;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: calc(var(--gap) * 1.25);
width: min(1100px, 100%);
}
.card {
background: linear-gradient(180deg, var(--panel) 0%, #0d1325 100%);
border: 1px solid var(--panel-border);
border-radius: var(--radius);
min-height: 220px;
padding: 1rem;
box-shadow: 0 1px 0 rgba(255,255,255,0.03) inset, 0 10px 30px rgba(0,0,0,0.25);
}
Advanced Tip: The –size custom property controls every preview shape in one place. You can theme multiple galleries by overriding –size, –accent, and –radius on a wrapper, without touching component CSS.
Step 3: The Centering With Grid
Now we switch the card to a simple single-cell grid and center its children with one declaration. The title sits beneath the shape using a small gap, and text-align keeps labels tidy.
/* CSS */
/* CSS */
.card {
display: grid;
place-items: center; /* align-items + justify-items in one line */
gap: 0.5rem;
text-align: center;
}
.card .title {
margin: 0;
font-size: 0.95rem;
color: var(--muted);
letter-spacing: 0.2px;
}
How This Works (Code Breakdown)
display: grid turns the card into a grid container. Even without rows or columns defined, a single implicit cell is created, which is perfect for simple centering use cases like badges, icons, and loaders.
place-items: center is a shorthand for align-items: center and justify-items: center. That means every direct child of the .card aligns to the center block-axis and center inline-axis inside the card’s content box. One declaration replaces two properties, and it reads clearly at a glance.
gap: 0.5rem inserts space between the shape and the title without extra margins that could complicate vertical centering. The grid gap is symmetrical and does not affect the position of the group inside the card.
text-align: center ensures label text lines up under the centered shape. text-align does not change layout flow, so the grid centering remains intact.
If you are using shapes elsewhere, you can mix and match with your existing utilities. For example, if you want to build custom previews, these guides walk through the fundamentals: how to make a circle with CSS, how to make a square with CSS, and how to make a triangle up with CSS. The shapes here are minimal on purpose, so the focus stays on centering with Grid.
One more alignment concept helps in real projects: place-items targets the alignment of items inside the grid tracks, while place-content targets how the grid itself sits inside the container when extra free space exists. For this card, place-items is the correct tool because we are aligning children within a single cell.
Step 4: Building the Shape Previews
These styles render the circle, square, and triangle. The triangle uses the border trick, which creates a zero-sized box whose colored borders form the visible shape. The card still centers it because the triangle element is the item being aligned, regardless of its intrinsic geometry.
/* CSS */
/* CSS */
.shape {
--current-size: var(--size);
inline-size: var(--current-size);
block-size: var(--current-size);
background: var(--accent);
filter: drop-shadow(0 6px 12px rgba(96,165,250,0.25));
border-radius: 10px;
}
/* Circle overrides only the rounding */
.shape.circle {
border-radius: 50%;
}
/* Square inherits the base .shape box; nothing more needed */
/* Triangle uses border trick, so we reset base box dimensions */
.shape.triangle-up {
inline-size: 0;
block-size: 0;
background: transparent;
border-left: calc(var(--current-size) / 2) solid transparent;
border-right: calc(var(--current-size) / 2) solid transparent;
border-bottom: var(--current-size) solid var(--accent);
border-radius: 0;
filter: drop-shadow(0 6px 12px rgba(96,165,250,0.25));
}
How This Works (Code Breakdown)
The .shape base class defines the size once, adds a background fill, and a soft shadow for depth. That base makes circle and square trivial. The circle only changes border-radius to 50%, while the square relies on the initial rounded rectangle and keeps its corners slightly softened.
The .shape.triangle-up resets width and height to zero because border-based triangles are made by coloring one border and making the other two borders transparent. Grid alignment still treats this triangle element as a box that occupies space based on its border widths. place-items centers it just like any other item.
Because the .card is a grid container, each visual element and the title share the same alignment rules. No extra wrappers or absolute positioning are needed. If you need a different alignment for a specific child, you can override with place-self: start end or a similar two-value shorthand on that child.
Advanced Techniques: Tweaks, Hover States, and Variants
Centered content benefits from animated feedback. These effects stay gentle and respect user settings. There is also a variant that switches centering to top-left for comparison, done with a single property change.
/* CSS */
/* CSS */
/* Focus and hover styling */
.card:where(:hover, :focus-within) .shape {
transform: translateZ(0) scale(1.06);
transition: transform 160ms ease, filter 160ms ease;
filter: drop-shadow(0 10px 20px rgba(96,165,250,0.35));
}
.card:where(:hover, :focus-within) .title {
color: var(--text);
transition: color 160ms ease;
}
/* Variant: align content to top-left without touching markup */
.card.variant-start {
place-items: start;
text-align: left;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.card .shape {
transition: none;
transform: none;
}
.card .title {
transition: none;
}
}
The :where() pseudo-class keeps the selector light and avoids specificity bloat. transform is GPU friendly at these scales, and drop-shadow on the shape produces consistent depth without painting large areas. The .variant-start class shows the benefit of shorthands: a single place-items: start moves both axes at once.
Accessibility & Performance
Accessibility
Decorative shapes are marked aria-hidden="true" so screen readers announce only the card titles. If your centered content conveys meaning on its own, expose a text alternative right next to it or add an aria-label on the parent container. Focus styles must be visible; the :focus-within trigger ties label highlighting to keyboard navigation. Keep contrast strong for labels against dark panels.
Motion should always be optional. The prefers-reduced-motion query disables the subtle hover scale and transitions. The component still works and still centers content without any animation.
Performance
Grid layout and place-items are computed once per container and are highly performant for typical card counts. The triangle uses borders, not extra markup or filters, so it is cheap to paint. Transforms animate on the compositor, which keeps hover effects responsive. The gallery uses repeat(auto-fit, minmax()) to reduce layout thrash during resize, and no JavaScript runs to maintain alignment.
Be aware that place-items on a flex container will not mirror this result. On flex, align-items works, but justify-items has no effect on row-direction flex boxes in current engines. Use Grid when you need two-axis centering with a single declaration.
The last closing paragraph
You learned what display: grid and place-items: center do and used them to build a responsive card gallery that centers content cleanly. You also saw how small variants and motion preferences fit into the same pattern. Keep this centering pattern in your toolbox and you will ship layouts that stay tidy at every size.