Home / Snippets / Layout /

Cover utility

Vertically center a principal element inside a minimum-height container, with optional header and footer pinned to the edges — all in pure CSS.

Site header

Hello, world

This heading is always centered in the available space.

Drag the bottom-right corner to resize the demo.

Widely Supported
layoutno-js

Quick implementation

.cover {
  display: flex;
  flex-direction: column;
  min-block-size: var(--cover-min-height, 100vh);
  padding: var(--cover-padding, 1rem);
}

/* Center every child by default */
.cover > * {
  margin-block: auto;
}

/* Un-pin the first child if it is not the centered element */
.cover > :first-child:not([data-centered]) {
  margin-block-start: 0;
}

/* Un-pin the last child if it is not the centered element */
.cover > :last-child:not([data-centered]) {
  margin-block-end: 0;
}

Mark the element you want centered with data-centered:

<div class="cover">
  <header>Site header</header>
  <main data-centered>
    <h1>Hello, world</h1>
  </main>
  <footer>Site footer</footer>
</div>

Prompt this to your LLM

Includes role, context, constraints, two variants, and edge cases to handle.

You are a senior frontend engineer implementing a "cover" layout utility
based on the Every Layout pattern.

Goal: A CSS class that vertically centers a principal element inside a
minimum-height container, while keeping an optional header pinned to the
top and an optional footer pinned to the bottom — no JavaScript.

Technical constraints:
- Use display: flex and flex-direction: column on the .cover container.
- Set min-block-size via a CSS custom property (--cover-min-height)
  with a fallback of 100vh. Never use height; the container must be
  able to grow beyond the minimum when content requires it.
- Use padding via a CSS custom property (--cover-padding) with a
  fallback of 1rem.
- Apply margin-block: auto to every direct child so each one gets
  equal free space above and below.
- Reset margin-block-start to 0 for :first-child:not([data-centered])
  and margin-block-end to 0 for :last-child:not([data-centered]).
  This pins non-centered first and last children to their respective edges.
- Use oklch() for any colors — no hex or rgba values.
- Use CSS custom properties (var(--card), var(--text), etc.) for theming.

Framework variant (pick one):
A) Vanilla CSS utility class .cover with HTML attribute data-centered
   on the principal child element.
B) React component — accept children, minHeight (string, default "100vh"),
   and padding (string, default "1rem") props; use a data-centered
   attribute or a centeredIndex prop to identify the principal element.

Edge cases to handle:
- If there is only one child (no header or footer), it should still be
  centered — margin-block: auto on a single flex child achieves this.
- When content in the principal element grows beyond the available space,
  min-block-size lets the container expand naturally; height would clip it.
- Avoid using justify-content: center on the container — that approach
  prevents the header/footer pinning trick from working.

Return CSS only (or a React component if variant B is chosen).

How margin-block: auto centers in a flex column

In a flex container, auto margins absorb all available free space on the axis they are applied to. When every child of a flex-direction: column container has margin-block: auto, each child gets an equal share of the leftover vertical space above and below it. If you have three children — header, main, footer — each gets one-third of the free space on each side, which means the middle element ends up visually centered and the outer two are pushed toward the edges.

The clever part of the cover pattern is the two :not([data-centered]) rules. They reset margin-block-start to 0 for the first child and margin-block-end to 0 for the last child — as long as those children are not the designated centered element. This collapses the outward margin of the header and footer, pinning them flush to the container's padding edge. All the free space that would have been split around the header and footer now flows to the principal element, centering it perfectly between them.

Why min-block-size instead of height

height: 100vh creates a fixed-size box. If the content inside — the hero heading, a subheading, a call-to-action button — grows taller than the viewport, it overflows and potentially overlaps other content. This is especially problematic on small screens or when text is enlarged for accessibility.

min-block-size: 100vh (the logical-property equivalent of min-height) sets a floor, not a ceiling. The container will be at least as tall as the viewport, guaranteeing the centered effect on spacious screens. But when content grows, the container grows with it — no overflow, no clipping, no broken layout. Content is always the source of truth for size; the minimum is just a promise that there will be enough room to make centering look intentional.

The custom property --cover-min-height makes this easy to adapt: set it to 50vh for a half-screen section, 20rem for a card, or leave it at the default 100vh for a full hero.

The header and footer pinning trick

The data-centered attribute is the signal that distinguishes the principal element from supporting elements. Without it, every child gets margin-block: auto, which distributes free space evenly — no single element is "the center." With it, the :not([data-centered]) selectors surgically remove the outward margins from the first and last children only, collapsing them to the container edge.

The result: the header sits flush against the top padding, the footer sits flush against the bottom padding, and all the remaining free space pools around the [data-centered] element, centering it between them. If you remove the header or footer from the markup, the rules simply don't match — the remaining children keep their auto margins and the layout degrades gracefully, still centering the principal element in the full space.

This approach requires no class toggling, no JavaScript, and no position: absolute. It is robust, composable, and works with any number of children as long as the first and last are the non-centered framing elements.