Composition Patterns
Visible Heading with Intro
Section intro text introduces the body content.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu nulla ut augue malesuada luctus.
Morbi suscipit sapien non ligula pretium, sed interdum justo scelerisque.
Visible intro with hidden semantic heading.
Using LayoutSection `label` provides an accessible name via `aria-label` on the `<section>` element, replacing the previous sr-only heading pattern.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu nulla ut augue malesuada luctus.
Morbi suscipit sapien non ligula pretium, sed interdum justo scelerisque.
Centered Content
Checks centered alignment and inverted token contrast.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu nulla ut augue malesuada luctus.
Morbi suscipit sapien non ligula pretium, sed interdum justo scelerisque.
Show code
{/* Visible heading with intro */}
<LayoutSection verticalPadding="xl">
<Heading level={3}>Visible Heading with Intro</Heading>
<p class="mt-sm">Section intro text introduces the body content.</p>
<p>Body content here.</p>
</LayoutSection>
{/* Screen-reader-only heading via LayoutSection label */}
<LayoutSection background="soft" verticalPadding="xl" label="Screen Reader Only Heading">
<p>Visible intro with hidden semantic heading.</p>
<p>Body content here.</p>
</LayoutSection>
{/* Centered contrast */}
<LayoutSection background="contrast" verticalPadding="xl">
<Heading level={3} class="text-center">Centered Content</Heading>
<p class="mt-sm text-center">Checks centered alignment.</p>
<p class="text-center">Body content here.</p>
</LayoutSection> Fragment
This page validates the getFragment() utility by loading and rendering a fragment from the fragments collection.
Fragment Demo
This fragment is used by the showcase to validate getFragment() integration.
Show code
import { getFragment } from "@/lib/content/fragments";
const fragment = await getFragment("demo-fragment");
const { Content } = fragment;
<LayoutSection background="soft" verticalPadding="sm" label="Fragment demo">
<Content />
</LayoutSection> Pullquote
Decorative pull quote with optional citation. Visually distinct from standard prose blockquotes.
SVG Quote (default)
Design is not just what it looks like. Design is how it works.
Text Quote
Simplicity is the ultimate sophistication.
No Quote Decoration (with citation)
Less, but better.
Show code
{/* SVG quote icon (default) */}
<Pullquote>
<p>Design is not just what it looks like. Design is how it works.</p>
</Pullquote>
{/* Text quote character */}
<Pullquote quoteStyle="text">
<p>Simplicity is the ultimate sophistication.</p>
</Pullquote>
{/* No quote decoration (with citation) */}
<Pullquote quoteStyle="none" citation="Dieter Rams">
<p>Less, but better.</p>
</Pullquote> TimeToRead
Reading time display with icon. Consumes the readingTimeMinutes value from the remark-reading-time plugin. The values below
are placeholder demonstrations.
Default label
5 min readCustom label
3 minute readUndefined (renders nothing)
← nothing rendered above
Show code
---
import TimeToRead from "@core/components/ui/TimeToRead.astro";
// In a content page, get the reading time from the remark plugin:
const { remarkPluginFrontmatter } = await render(entry);
const readingTimeMinutes = remarkPluginFrontmatter?.readingTimeMinutes;
---
{/* Typical usage on an article page */}
{entry.data.readingTime && (
<TimeToRead minutes={readingTimeMinutes} />
)}
{/* Custom label */}
<TimeToRead minutes={readingTimeMinutes} label="{n} minute read" /> Columns
Generic N-column CSS grid layout with three column patterns: equal, asymmetric, and auto-fit.
Equal Columns (3)
Column 1
Column 2
Column 3
Asymmetric (2fr 1fr)
Wide column (2fr)
Narrow (1fr)
Auto-fit
Card 1
Card 2
Card 3
Card 4
No Collapse
Always 3 columns
Even on mobile
No collapse
Show code
{/* Equal columns — collapses below md (default) */}
<Columns columns={3}>
<div>Column 1</div>
<div>Column 2</div>
<div>Column 3</div>
</Columns>
{/* Asymmetric — also collapses below md by default */}
<Columns columns="2fr 1fr">
<div>Wide column</div>
<div>Narrow sidebar</div>
</Columns>
{/* Auto-fit responsive grid */}
<Columns columns={{ count: "auto-fit", min: "12rem" }}>
<div>Card 1</div>
<div>Card 2</div>
</Columns>
{/* Never collapse */}
<Columns columns={3} collapseBelow="none">
<div>Always visible</div>
<div>Even on mobile</div>
<div>No collapse</div>
</Columns>
{/* Collapse below lg instead of md */}
<Columns columns={2} collapseBelow="lg">
<div>Left</div>
<div>Right</div>
</Columns> MediaText
Content-level split layout pairing an image with text. Supports reverse order and split ratio variants.
Default (image left)
Image and Text
Text content sits alongside the image. The default split is equal (1:1). On mobile, the layout stacks with the image above the text.
Reversed (image right)
Reversed Layout
The image moves to the right side. On mobile, the image still appears above the text for consistent reading flow.
Wide Content Split
More Text Space
The content column is wider than the image column (3:2 ratio). Vertical alignment is set to "start" so content aligns to the top.
Show code
{/* Default: image left, equal split */}
<MediaText image={{ src: "/images/photo.jpg", alt: "Description" }}>
<h3>Heading</h3>
<p>Text content alongside the image.</p>
</MediaText>
{/* Reversed: image right */}
<MediaText image={{ src: "/images/photo.jpg", alt: "Description" }} reverse>
<h3>Reversed</h3>
<p>Image on the right side.</p>
</MediaText>
{/* Wide content, top-aligned */}
<MediaText
image={{ src: "/images/photo.jpg", alt: "Description" }}
split="wide-content"
verticalAlign="start"
>
<h3>Wide Content</h3>
<p>Content column is wider.</p>
</MediaText> Table
Responsive styled table wrapper with scroll and stack mobile modes.
Scroll Mode (default)
| Name | Role | Department | Status | Location |
|---|---|---|---|---|
| Alice Johnson | Developer | Engineering | Active | Remote |
| Bob Smith | Designer | Product | Active | New York |
| Carol Davis | Manager | Operations | On Leave | London |
Stack Mode
| Name | Role | Status |
|---|---|---|
| Alice Johnson | Developer | Active |
| Bob Smith | Designer | Active |
Show code
{/* Scroll mode (default) with striped rows */}
<Table striped>
<table>
<thead>
<tr><th>Name</th><th>Role</th><th>Status</th></tr>
</thead>
<tbody>
<tr><td>Alice</td><td>Developer</td><td>Active</td></tr>
</tbody>
</table>
</Table>
{/* Stack mode — cells become blocks on mobile */}
<Table responsive="stack" hoverable>
<table>
<thead>
<tr><th>Name</th><th>Role</th><th>Status</th></tr>
</thead>
<tbody>
<tr>
<td data-label="Name">Alice</td>
<td data-label="Role">Developer</td>
<td data-label="Status">Active</td>
</tr>
</tbody>
</table>
</Table> Cover
Background image with overlay and positioned content. Overlay opacity and color are configurable.
Default (centered)
End Position, Higher Opacity
Show code
{/* Default: centered content */}
<Cover image={{ src: "/images/hero.jpg", alt: "Background" }}>
<h2>Cover Heading</h2>
<p>Overlay content.</p>
</Cover>
{/* Bottom-aligned, higher opacity */}
<Cover
image={{ src: "/images/hero.jpg", alt: "Background" }}
contentPosition="end"
overlayOpacity={0.7}
minHeight="20rem"
>
<h2>Bottom Content</h2>
</Cover> Timeline
Vertical timeline for project histories, about pages, and changelogs.
-
Project Started
Initial planning and requirements gathering phase.
-
Design Phase
Wireframes, visual design, and component library established.
-
Development
Core implementation with iterative review and testing.
-
Launch
Site launched successfully with all milestones complete.
Show code
<Timeline>
<TimelineItem title="Project Started" date="January 2026">
<p>Initial planning and requirements gathering.</p>
</TimelineItem>
<TimelineItem title="Design Phase" date="February 2026">
<p>Wireframes and visual design completed.</p>
</TimelineItem>
<TimelineItem title="Launch" date="March 2026">
<p>Site launched successfully.</p>
</TimelineItem>
</Timeline>