Skip to content
AstroCraft Docs
On this theme

Layout & Spacing

Olsa has no Container component. Page width is a handful of @apply classes in src/styles/global.css, applied directly to the elements that need them. That is a smaller mechanism than a wrapper component, and it lets a section put the frame on an inner row while keeping a full-bleed background — which several sections do.

Every one of these classes exists because the same recipe was repeated three or more times. That threshold is the house rule; two repeats stay inline.

The width classes

Class Renders Used by
.site-container mx-auto max-w-[1100px] px-4 narrow editorial columns
.site-band mx-auto w-[95%] the full-bleed panel strip and the navbar’s resting width
.section-container mx-auto flex max-w-7xl flex-col gap-14 px-4 sm:gap-16 sm:px-6 the standard content column

.site-band is the one that shapes the page’s silhouette. The dark GrainyPanel sections — the hero, the CTA, the 404 — are centred 95%-width strips rather than full-bleed, and the navbar’s at-rest width matches them so the bar reads as part of the hero panel. One class means those cannot drift apart.

.section-container carries its own vertical gap, so a section’s children stack with consistent rhythm without each one restating gap-14 sm:gap-16.

Genuinely different containers stay inline. The FAQ uses a max-w-3xl reading column; the About story uses a split row. Neither is forced into a shared class it would have to fight.

The vertical rhythm

.section-band {
  @apply bg-background py-20 sm:py-28 lg:py-32;
}

.section-band is the theme band plus its vertical padding, and it is what most light sections open with. Paired with .section-container it is the whole scaffold:

<section class="section-band" aria-labelledby="values-heading">
  <div class="section-container">
    <!-- content -->
  </div>
</section>

That pattern — band on the <section>, container on an inner <div> — is what lets a background run edge to edge while the content stays framed.

There is a fourth class for a specific problem:

.hero-clearance {
  @apply pt-28 sm:pt-32 lg:pt-36;
}

The navbar is fixed, so light page heroes need top padding that clears it. Rather than three sections each guessing at the navbar’s height, that fact lives in one place. If you change the navbar’s height, this is the class to update.

The panel classes

Four classes carry the dark-panel recipe.

.panel-shell {
  @apply rounded-[2rem] sm:rounded-[2.5rem] lg:rounded-[3rem];
}

.panel-shell is opt-in, and the reason is worth understanding because it generalises. Four of the seven GrainyPanel call sites want this responsive radius trio; three want a different shape. Making it a default on the primitive would have been the obvious move — and it would have broken those three, because tailwind-merge only dedupes within a variant scope. A caller passing rounded-2xl would kill the base rounded-[2rem] and leave the sm: and lg: steps alive, producing a shape nobody asked for at 640px.

So the trio is a class the four panels opt into, and the other three keep their own inline radius. There are no partial-override semantics to get wrong.

The same reasoning explains .panel-title and .panel-lede in Typography: shared classes carry only what is genuinely uniform, and anything a caller might want to override stays at the call site.

Breakpoints

Declared explicitly in tailwind-theme.css, including one Tailwind does not ship:

--breakpoint-xs: 400px;
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;

Everything from sm up mirrors Tailwind’s defaults and is restated so the set is visible in one place. xs: 400px is the addition, for the narrow-phone adjustments a few components need.

lg is the meaningful structural breakpoint in this theme. It is where the navbar swaps between the horizontal bar and the mobile sheet, and where the blog post’s sticky table of contents appears.

Global layout behaviour

Three declarations on <html> in global.css are easy to miss and matter:

html {
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}

scrollbar-gutter: stable reserves the scrollbar’s space so a page that grows past the viewport does not shift horizontally.

scroll-behavior: smooth is what makes anchor links glide, and it is neutralised in two places. The reduced-motion guard in motion/index.css forces it to auto. And there is a subtler override:

html[data-astro-transition] {
  scroll-behavior: auto;
}

The view-transitions router stamps that attribute on <html> during a swap. Without the override, the router’s post-swap scroll restoration inherited smooth scrolling, so every page change glided from the old scroll offset to zero — firing every scroll reveal on the way past, which meant sections arrived already animated. Suspending smooth scrolling under the attribute makes the reset instant while keeping it for real anchor navigation.

If you ever see reveals playing before you scroll to them after a navigation, that override is the first thing to check.

Landmark structure

BaseLayout renders <slot name="header" />, then <main> wrapping the default slot, then <slot name="footer" />. Rendering the navbar and footer into the named slots is what lands <header> and <footer> as siblings of <main> — a real banner and a real contentinfo landmark rather than two elements nested inside main. See Pages & Routing.

<body> carries min-h-[100lvh]. The large-viewport unit, not 100vh, so mobile browser chrome does not create a scrollable sliver on short pages.

Adding a shared layout class

Ask first whether it is repeated three or more times across genuinely unrelated elements. If yes, add it to @layer components in global.css with a comment saying what it is for and which call sites share it — every existing one does, and those comments are how the next person knows whether their new case belongs.

If it has structure or variants rather than being one flat recipe, it should be a component or a tailwind-variants config instead. That is what src/components/ui/ is for; see UI Components.

And remember the override rule: a class with responsive steps cannot be partially overridden at the call site. If callers will need to vary it, make it opt-in like .panel-shell rather than a default.

NEXT STEPUI Components