Compose Page: $ARGUMENTS
Build or update a full site page from approved components. Pages are living documents — this skill handles both creation and evolution.
Scope
This skill does two things:
- Design system layouts — full-page compositions in
src/pages/design-system/layouts/using real components with placeholder content. These are specimens, not production pages. - Site pages — real routes in
src/pages/with production content, database queries, SEO, and site layout wrappers.
Intent check: When the user says "update the home page" or similar, clarify before proceeding: "Do you mean the home layout in the design system (/design-system/layouts/home), or the actual website home page at src/pages/index.astro?"
Before you start
- Read
src/system.md— intent, accessibility standard, anti-patterns. - Read
src/styles/global.css— available tokens for page-level spacing and backgrounds. All color references must use semantic token utilities — raw palette classes don't exist. - Read design-principles.md — especially Spacing & Layout, Dark Mode, and Finishing Touches sections.
- Read tailwind-conventions.md — Tailwind v4 syntax rules.
- Inventory components — list
src/components/*.astroto know what's available. Only use approved, built components. - Read
src/components/Section.astro— understand the Section pattern before composing pages. Section accepts:seamless,fullWidth(drops flow width constraint),narrow(constrains to--container-narrow),withWrap(re-constrains children of full-width sections),withPadding(adds vertical padding without seamless),title(renders<h2>with automaticaria-labelledby). - Check for wireframe — look for
src/pages/design-system/wireframes/<page-name>.astro. If it exists, read it — especially the data source annotations in frontmatter. - Check for layout — look for
src/pages/design-system/layouts/<page-name>.astro. If a layout exists, it's an approved composition showing how components assemble for this page. Use it as the reference — match its section order, component choices, and visual rhythm. The real page replaces placeholder content with real data but follows the same structure. - Check for existing page — look for
src/pages/<page-name>.astro. If it exists, read it first. This is an evolution, not a rewrite. - Check for site layout — look in
src/layouts/for existing site layouts (header/footer wrappers).
Entry points
This skill can be invoked with:
- A wireframe reference — "compose the pricing page" (where a wireframe exists)
- A component list — "build a page with hero, features, and CTA"
- A description — "build our about page"
If no wireframe exists, recommend one ("Want me to sketch a wireframe first with /angora-wireframe?") but don't force it.
Mandatory gate: static vs template per section
Before building, determine the data source for every section on the page.
If a wireframe exists with data source annotations, read them:
/*
Data sources:
sections:
- component: Hero
data: static
- component: Testimonials
data: table:testimonials
- component: Pricing
data: table:pricing_tiers
*/
If no wireframe or annotations, ask for each section:
- "Will this content change? Is there more than one?" → template (table reference)
- "Is this one-off copy edited in code?" → static
For template sections, verify the table exists by globbing src/data/schema/tables/*.ts — each file is a table definition.
If a required table doesn't exist, flag it: "The testimonials table doesn't exist yet. This needs schema work first. Run /angora-schema testimonials to design it."
List/detail pattern
Recognize when cards link to individual pages (blog posts, case studies, cities, team members). This is a first-class pattern:
List page
src/pages/<collection>.astro
- Query:
SELECT * FROM <table> WHERE status = 'published' - Renders cards/list items linking to
/collection/[slug]
Detail template
src/pages/<collection>/[slug].astro
- Uses
getStaticPaths()to generate pages from database - Query:
SELECT * FROM <table> WHERE status = 'published' - Wire up SEO from table fields:
<title>,<meta name="description">, OG tags - Only render published records
---
import db from '../../data/db.ts';
import { <table> } from '../../data/schema/tables/<table>';
import { eq } from 'drizzle-orm';
export function getStaticPaths() {
const items = db.select().from(<table>).where(eq(<table>.status, 'published')).all();
return items.map(item => ({
params: { slug: item.slug },
props: { item },
}));
}
const { item } = Astro.props;
---
<html>
<head>
<title>{item.metaTitle || item.title}</title>
<meta name="description" content={item.metaDescription || ''} />
</head>
...
</html>
Site layout
Ask: "Should this page have the site header/footer, or is it a standalone landing page?"
Check for src/layouts/ — if no layout exists and user wants header/footer, flag it: "No site layout exists yet. Want me to create one, or should we build this as a standalone page for now?"
Site layouts go in src/layouts/ (Astro convention). A site layout wraps page content with shared header/footer/nav.
Output
src/pages/<page-name>.astro— the site page- Optional:
src/pages/<collection>/[slug].astro— for list/detail patterns
Form sections
When a page includes forms (contact, signup, settings), use the form layout primitives:
- FieldGroup — vertical stack of form rows with standard
grid-gapspacing - FormRow — horizontal row within a FieldGroup. Children grow to fill space by default. Use
grow={false}for button rows
Every field inside a FieldGroup should be wrapped in a FormRow. Don't use raw <div> wrappers for form layout.
Page-level concerns
- Page wrapper uses
section-flow—<main class="section-flow">wraps all sections. Thesection-flowutility lives inglobal.cssand controls inter-section spacing (gap controlled by--section-gap). For editorial content (blog posts, articles), use<article class="prose-flow">instead. - Ambient prose —
section-flowandprose-flowinclude prose automatically via@apply prose. Raw HTML elements get typographic styling from prose at low specificity (:where()) — component classes always win. Don't addproseseparately. The only localproseusage is for nested content zones that need their own vertical rhythm. - Landmark components live on their own — Hero, Footer, Nav go directly inside
section-flow. They render their own semantic element withdata-componentand flow attributes (data-seamless,data-full-width). Don't wrap landmarks in<Section>. - Content components go inside
<Section>— the consumer controlstitle,seamless,narrow,fullWidth,withWrap. Use<Section fullWidth withWrap>when a section needs edge-to-edge background but children should stay constrained to container width. - Backgrounded sections use
seamless—<Section seamless>for sections with background colors/images. Adjacent seamless sections get 0 gap so backgrounds butt up. - Background alternation for visual rhythm — use semantic surface tokens for section variety. These automatically adapt in dark mode
- Visual flow — the eye moves naturally through the page
- Responsive behavior via container queries
- SEO:
<title>,<meta name="description">, OG tags color-schememeta tag — already handled byLayout.astrofor design system pages. For site pages, include<meta name="color-scheme" content="light dark">if the site supports dark mode, or<meta name="color-scheme" content="light">if light-only- Images in dark mode — photographs on dark backgrounds can feel jarring. Use
dark:brightness-80 dark:contrast-125on<img>elements to dim slightly. Avoid images with baked-in white backgrounds — they become glowing rectangles in dark mode. Prefer transparent or neutral backgrounds for logos and illustrations
Layout purity rules (design system layouts only)
When building a layout in src/pages/design-system/layouts/, enforce these additional rules:
- Single-file rule — a layout page is one
.astrofile. No extracted sub-components, no helper modules. If the layout feels too complex for one file, the components are too coarse — refine them in/angora-component. - Zero-class layout rule — layouts are pure component composition. No Tailwind utility classes on raw HTML elements. If you need to add a class to a
<div>, a component is missing. Allowed: justsection-flowon<main>. Not allowed: spacing wrappers (<div class="mt-8">), grid layouts (<div class="grid grid-cols-2">), visual treatments (<div class="bg-card rounded-lg">), styled headings, width constraints, Button wrapper divs,classoverrides on components, raw markup. - No client-side code — design system layouts are static. No inline
<script>tags for interactivity. If a section needs interactivity, it should use a Preact island via the component itself. - Layout purity check — after building, verify: single file? No client code? Zero-class compliance? Landmark components outside
<Section>? Content components inside<Section>? No arbitrary values? No double-wrapping? Background rhythm makes sense?
Review
Present the completed page to the user and suggest they review in browser (pnpm dev). Then run /angora-design-system-audit automatically — this is verification, not a change. Present findings and proposed fixes. Wait for approval before applying fixes. (Accessibility is covered at the component and layout level by pnpm test:a11y — no need to re-test assembled pages.)