A production-ready Astro 6 starter template for government service websites.
USWDS-native · Section 508 / WCAG 2.1 AA · i18n (en + es) · Zero client JS · Deploy anywhere
Click "Use this template" on GitHub to create your own repository, then run the setup wizard:
pnpm install
node scripts/init-agency.mjs # configure your agency name, domain, and identifier links
pnpm devYour site will be at http://localhost:4321.
| Category | Details |
|---|---|
| Design system | USWDS 3.x via SCSS — no reimplementation |
| Accessibility | Section 508 / WCAG 2.1 AA — skip nav, landmarks, focus management |
| Required components | USA Banner + USA Identifier on every page |
| Component library | 20 thin Astro wrappers for USWDS patterns |
| i18n | English (/) and Spanish (/es/) — one route file per page serves both locales |
| Search | Pagefind static search at /search/ — self-hosted, language-aware |
| Content collections | Services, announcements, FAQs — Zod-typed, MDX-ready |
| CI quality gates | TypeScript, HTML, axe-core, Lighthouse, links, plain language, USWDS compliance |
| Output | Static — deploys to GitHub Pages, Cloud.gov Pages, Netlify, Vercel, S3 |
- Click Use this template → Create a new repository
- Clone your new repo and install:
git clone https://github.com/YOUR-ORG/YOUR-REPO.git cd YOUR-REPO pnpm install - Run the agency setup wizard:
This updates
node scripts/init-agency.mjs
src/config/site.tswith your agency name, domain, and the seven required USA Identifier links. - Replace seed content in
src/content/with your programs and FAQs. pnpm dev— live preview athttp://localhost:4321pnpm build— production build indist/
All agency-specific settings live in one file:
// src/config/site.ts
export const siteConfig = {
name: 'Department of Human Services',
shortName: 'DHS',
domain: 'dhs.state.gov',
description: 'Programs and services for eligible residents.',
locale: 'en-US',
// Required links for the USA Identifier footer component
links: {
about: '/about/',
accessibility: '/accessibility/',
foia: 'https://www.foia.gov/',
noFear: 'https://www.eeoc.gov/no-fear-act-data',
oig: 'https://www.oversight.gov/',
privacy: '/privacy/',
budget: '/',
usagov: 'https://www.usa.gov/',
},
} as const;src/
├── config/site.ts # Agency name, domain, identifier links ← edit this first
├── content/ # Markdown content (services, faqs, announcements)
├── components/uswds/ # 20 USWDS pattern wrappers (Alert, Accordion, Hero, …)
├── i18n/ # Translation strings (en, es) + useTranslations helper
├── layouts/ # BaseLayout, ServiceLayout, ApplyLayout
├── pages/
│ ├── [...lang]/ # One route file per page serves BOTH / and /es/
│ │ ├── index.astro # (locale comes from getStaticPaths via localeStaticPaths())
│ │ ├── services/ # services index + [slug] detail pages
│ │ ├── apply/[program].astro
│ │ └── search.astro # Pagefind-powered site search
│ ├── 404.astro
│ └── internal/components.astro # Dev-only component preview (not linked publicly)
└── styles/
├── uswds-theme.scss # USWDS entry point (settings + full import)
└── globals.css # Tailwind utilities + custom CSS
scripts/
├── init-agency.mjs # One-time agency setup wizard
├── setup-uswds.mjs # Copies USWDS assets to public/uswds/
├── plain-language.mjs # Flesch-Kincaid readability gate (avg grade ≤ 8)
├── compliance-check.mjs # USWDS compliance check on built HTML
└── check.sh # Runs all local quality gates
All 20 components live in src/components/uswds/. Preview them at /internal/components/ during local dev.
| Component | USWDS pattern |
|---|---|
Alert / SiteAlert |
Alert |
Accordion / AccordionItem |
Accordion |
Breadcrumb |
Breadcrumb |
CallToAction |
Banner section pattern |
Footer |
Footer |
Header |
Header |
Hero |
Hero |
Pagination |
Pagination |
ProcessList / ProcessListItem |
Process list |
Search |
Search |
ServiceCard |
Card |
SideNav |
Side navigation |
StepIndicator |
Step indicator |
SummaryBox |
Summary box |
Tag |
Tag |
USABanner |
Banner |
USAIdentifier |
Identifier |
Run all checks locally before pushing:
bash scripts/check.sh # TypeScript + build + HTML + plain language + compliance
bash scripts/check.sh --verbose # same, with per-file output| Gate | Tool | Threshold |
|---|---|---|
| TypeScript | astro check |
0 errors |
| HTML | html-validate |
0 errors |
| Accessibility | @axe-core/cli (CI only) |
0 violations |
| Lighthouse | @lhci/cli (CI only) |
perf ≥ 90, a11y = 100, BP ≥ 90, SEO ≥ 90 |
| Links | linkinator (CI only) |
0 broken internal links |
| Plain language | scripts/plain-language.mjs |
avg Flesch-Kincaid grade ≤ 8 |
| USWDS compliance | scripts/compliance-check.mjs |
banner + identifier + skip nav present |
Push to main. The workflow in .github/workflows/deploy-pages.yml deploys automatically.
Enable Pages: Settings → Pages → Source: GitHub Actions.
⚠️ Security headers: GitHub Pages does not support custom HTTP response headers, so the headers inpublic/_headers(clickjacking protection viaframe-ancestors,X-Frame-Options, HSTS, etc.) are silently ignored on GitHub Pages. They only take effect on Netlify or Cloudflare Pages. If you need these protections — and production government sites should have them — deploy to a host that supports response headers, or put a CDN/reverse proxy (e.g. CloudFront) in front of Pages and set the headers there.
# federalist.json
{
"build": {
"command": "pnpm install && pnpm build",
"destination": "dist"
}
}Both auto-detect Astro. Set build command to pnpm build and publish directory to dist.
pnpm build # outputs to dist/Upload the contents of dist/ to your bucket or CDN origin.
Site search is powered by Pagefind. The pnpm build
script indexes dist/ after the Astro build and writes the index to
dist/pagefind/. Results are served at /search/ (English) and /es/search/
(Spanish) — Pagefind splits the index by each page's <html lang>, so each
locale searches its own content.
- Search only works on a production build (
pnpm build && pnpm preview); the dev server has no index. - The CSP includes
'wasm-unsafe-eval'because Pagefind runs its index as same-origin WebAssembly (this does not allow JavaScripteval()). - No third-party requests: the index and UI are self-hosted static files.
The contact form (/contact/) and eligibility screeners (/apply/*) are
accessible scaffolding only — they do not submit anywhere out of the box.
Before launch, wire the action attributes to your backend of choice
(Formspree, API Gateway + Lambda, your agency's form service, etc.). Look for
the TODO (M3) comments in src/pages/[...lang]/contact.astro and
src/pages/[...lang]/apply/[program].astro.
The template ships with English (/) and Spanish (/es/). Every page lives
once in src/pages/[...lang]/ and renders for each locale returned by
localeStaticPaths(), so there is no page tree to mirror. To add another
language (e.g. French):
- Add
fr: 'fr-FR'toLOCALESinastro.config.mjs - Add
'fr'to theLocaletype andlocalesarray, and registerfr.jsonin thetranslationsmap, insrc/i18n/utils.ts(createsrc/i18n/fr.jsonby copyingen.jsonand translating) - Add a
frentry to each page'scopyobject insrc/pages/[...lang]/*.astro— TypeScript will point you at every one - Add translated fields to content collections as needed (see
esSlug/esQuestioninsrc/content.config.tsfor the pattern) - Add the locale to the hreflang/og:locale logic in
src/layouts/BaseLayout.astroand the language toggle in the Header
See COMPLIANCE.md for Section 508, WCAG 2.1 AA, 21st Century IDEA Act, and USWDS version details.
See CONTRIBUTING.md.
Code: MIT
Seed content: CC0 — no rights reserved, free to adapt for any agency.