Centered Screen
A full-viewport centering wrapper. Renders a <section> that pins its children to the optical centre of the screen using a flex column with a configurable minimum height — useful for empty states, splash screens, sign-in surfaces, 404 pages, and terminal-style "you have arrived" routes. The wrapper owns vertical centering, horizontal centering, a max-width clamp, a padded gutter, and a subtle opacity fade-in on mount. It does not own the content — drop any node.
Welcome back
Pick up where you left off — your last session is still warm and a fresh stretch of cards is ready when you are.
Installation
npx shadcn@latest add https://craftbits.dev/r/centered-screen.jsonUsage
Wrap any column of content. The wrapper handles the gutter, the max-width clamp, and the vertical centering.
import { CenteredScreen, Button } from "@craft-bits/core";
<CenteredScreen minHeight="screen">
<h1>Welcome back</h1>
<p>Pick up where you left off.</p>
<Button variant="primary">Resume session</Button>
</CenteredScreen>Tune the horizontal clamp for narrower forms (sign-in / sign-up):
<CenteredScreen minHeight="screen" maxWidth="sm">
<SignInForm />
</CenteredScreen>Or drop the clamp entirely when the child owns its own width:
<CenteredScreen minHeight="tall" maxWidth="full">
<Splash />
</CenteredScreen>Understanding the component
- Landmark by default. Always renders a
<section>so the wrapper is announced as a self-contained region. Pair witharia-labelwhen the screen needs a name (e.g."Sign in"). - Vertical span via prop.
minHeight="screen"pins the content to the viewport centre (min-h-screen);tallreserves 60vh (the source-project default — sits inside a longer scroll page);autodisables the clamp so the wrapper sizes to its content. - Horizontal clamp. The content column is centred via
mx-autoand clamped bymaxWidth(default2xl). Passfullto drop the clamp when the child already manages its own width. - Stacked rhythm. Children stack as a flex column with
gap-6between siblings. The gutter ispx-6so content never bleeds against a narrow viewport edge. - Subtle fade on mount. The wrapper fades in from
opacity: 0 → 1viaSPRINGS.smooth. Honoursprefers-reduced-motion— the static surface renders immediately when the user opts out. PassdisableMotionto skip the fade when nested inside an already-animated container. - Data-attributes for nested styling. The root carries
data-cb-centered-screen,data-min-height, anddata-max-widthso consumers can target nested regions without re-deriving props.
Variants
Splash (full screen, narrow column)
<CenteredScreen minHeight="screen" maxWidth="sm">
<Wordmark />
<SignInForm />
</CenteredScreen>Empty state inside a card body
<CenteredScreen minHeight="auto" maxWidth="md">
<EmptyIcon />
<p>No items yet — add one to get started.</p>
</CenteredScreen>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The content to centre. Stacks as a flex column with gap-6. |
maxWidth | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full' | '2xl' | Horizontal clamp for the content column. full disables the clamp. |
minHeight | 'screen' | 'tall' | 'auto' | 'tall' | Vertical span. screen fills the viewport, tall reserves 60vh, auto disables. |
disableMotion | boolean | false | Skip the opacity fade-in on mount. The wrapper still respects prefers-reduced-motion regardless. |
className | string | — | Merged onto the rendered <section>. |
Accessibility
- The root renders a
<section>element. Inside a<main>or<article>, that means assistive tech announces the wrapper as a self-contained region. Pair witharia-labelwhen the screen has a name (e.g.aria-label="Sign in"). - No interactive affordances are added by the wrapper. Focus order flows through the children in source order.
- Color contrast: the wrapper itself contributes no foreground — only the body's
--cb-fgand any nested--cb-fg-mutedtext. Both pass WCAG AA against--cb-bgand--cb-bg-elevated. - The mount fade respects
prefers-reduced-motion— the wrapper renders the static surface immediately when the user has reduced-motion on. - The fade is opacity-only (no transform, no displacement), so no layout-thrash or scroll jump at the moment of mount.
Credits
- Extracted from:
AlgoFlashcards(src/platform/ui/CenteredScreen.tsx). The original was a 20-linemotion.divthat tookchildren + classNameand rendered a flex column withmin-h-[60vh],max-w-2xl, and an inlineSPRING.smoothfade-in. craft-bits keeps the surface intent intact and widens the API tomaxWidth(7 stops includingfull),minHeight(screen/tall/auto), anddisableMotion. The container is lifted from<div>to<section>for landmark semantics, the inline spring is rewired toSPRINGS.smoothfrom@craft-bits/core/motion, and the wrapper now honoursprefers-reduced-motionexplicitly.