Skip to content

New sections

Every bundled section is a single .astro file in src/components/. Building a new one is just creating another file. This page walks through the pattern.

A new section starts here:

src/components/Awards.astro
---
interface Props {
heading?: string;
awards?: Array<{ year: string; title: string; org: string }>;
}
const {
heading = "Recognition",
awards = [
{ year: "2025", title: "Product of the Year", org: "Indie Hackers" },
{ year: "2024", title: "Webby Award", org: "Webby" },
],
} = Astro.props as Props;
---
<section class="py-20 md:py-28">
<div class="container-content">
<h2 class="text-display-sm reveal">{heading}</h2>
<ul class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6">
{
awards.map((award) => (
<li class="reveal p-6 bg-surface border border-line-soft rounded-2xl">
<div class="text-eyebrow">{award.year}</div>
<div class="mt-2 text-[1.05rem] font-semibold text-strong">{award.title}</div>
<div class="mt-1 text-[14px] text-soft">{award.org}</div>
</li>
))
}
</ul>
</div>
</section>

Drop it into any page:

src/pages/about.astro
import Awards from "~/components/Awards.astro";
// …
<Awards />

It already follows the theme’s spacing, container, surface, and typography conventions.

Every theme exposes a small palette of utility classes that map to brand tokens. Reach for these first — they’re what keep new sections visually consistent with shipped ones.

TokenUse
container-contentStandard horizontal container (78rem max)
container-wideWider container (90rem max)
bg-bgPage background
bg-surfaceCard / subtle surface
bg-elevatedElevated surface (darker than surface)
bg-brand-softBrand-tinted soft background
text-strongHeadings, emphasized text
text-textDefault body text
text-softSecondary body
text-mutedCaptions, helper text
text-brandLinks, accents
border-lineStrong dividers
border-line-softSubtle dividers
text-eyebrowPre-heading “EYEBROW” treatment
text-displayBig display headings
text-display-smSection-level display headings
btnBase button reset
btn-brandPrimary brand button
btn-ghostSecondary outline button
btn-lgLarge size modifier
chipSmall rounded label/badge
revealAdd to any element for scroll-reveal in

If your new section’s data should be editable without touching the component, follow the bundled pattern:

src/components/Awards.astro
---
import { siteConfig } from "~/config/site";
const a = (siteConfig as any).awards; // optional block
const heading = a?.heading ?? "Recognition";
const items = a?.items ?? [];
---
{items.length > 0 && (
<section class="py-20 md:py-28">
{/* …same as above, using `heading` and `items`… */}
</section>
)}

Then add the matching block in site.ts:

src/config/site.ts
awards: {
heading: "Recognition",
items: [
{ year: "2025", title: "Product of the Year", org: "Indie Hackers" },
],
},

The section auto-hides if items is empty — handy when you reuse the theme for clients who don’t have any.

The theme already loads an IntersectionObserver script in Layout.astro that watches for .reveal elements. Add the class, optionally a data-delay:

<div class="reveal" data-delay="1">…</div>
<div class="reveal" data-delay="2">…</div>

data-delay is 1–6; each step is ~80ms. No setup, no JS for you to write.

For section-specific styling that doesn’t fit Tailwind utilities, scope it with <style>:

<section class="awards-grid">
{/* … */}
</section>
<style>
.awards-grid {
background: linear-gradient(to bottom, var(--color-bg), var(--color-surface));
}
</style>

Astro automatically scopes the styles to this component — no class name collisions.

Open src/components/Hero.astro or src/components/Pricing.astro from any theme. You’ll find the same skeleton:

---
// 1. Imports
import Icon from "~/components/Icon.astro";
import { siteConfig } from "~/config/site";
// 2. Props with sensible defaults
const { heading = "..." } = Astro.props;
// 3. Local computations (optional)
const items = siteConfig.something.items;
---
<!-- 4. Outer <section> with vertical rhythm classes -->
<section class="py-20 md:py-28">
<!-- 5. Container -->
<div class="container-content">
<!-- 6. Heading + subtext -->
<h2 class="reveal text-display-sm">{heading}</h2>
<!-- 7. Content grid / list -->
{/* … */}
</div>
</section>

Match this skeleton, your new section reads as part of the family.