Overview
This document covers the coding conventions and development standards for the project. It consolidates rules from AGENTS.md and the project charter into a single reference for human developers. For component-specific authoring patterns, see DOC-00040 (Component Authoring).
File naming
- Components:
PascalCase.astro(e.g.,HeroSection.astro,PricingCard.astro) - Utilities and helpers:
kebab-case.ts(e.g.,format-date.ts,get-collection.ts)
Ownership markers
Every source file carries a top-of-file ownership comment:
// CORE-OWNED— Theme framework. These files are shared across all sites built on the platform. Modify only when changing core behavior.// SITE-OWNED— Per-site customization. These files are specific to an individual client site.
The ownership split follows a split-concern pattern: core defines the schema and default behavior, site provides the instance-specific values. For example, src/core/config/ defines config schemas while src/site/config/ provides the site’s actual configuration.
Key directories
| Directory | Ownership | Purpose |
|---|---|---|
src/core/ | CORE-OWNED | Theme framework, shared components |
src/site/ | SITE-OWNED | Site config, theme overrides, assets |
src/content/ | SITE-OWNED | Content collections |
src/pages/ | SITE-OWNED | Route files |
src/pages/api/ | CORE-OWNED | API routes (exception to above) |
src/lib/ | CORE-OWNED | Utilities, contracts, helpers |
Component sourcing
Components are sourced from a tiered priority list. Only build custom when no tier-1 source fits:
- Tailwind Plus Blocks — First choice for layout and section components.
- Flowbite — UI components and patterns.
- Starwind — Accessible Tailwind-native components and design blocks.
- Font Awesome Pro — Icons (manually copied SVGs, not runtime loaded).
- Custom — Last resort, when no existing source meets the need.
Hydration policy
The default rendering mode is zero-JS. Pages ship no client-side JavaScript unless explicitly justified.
- Use vanilla TypeScript for interactive islands that require client-side behavior.
- Every hydrated component needs an explicit justification — interactivity that cannot be achieved with CSS alone.
- The project uses Astro’s SSG-first approach with hybrid mode reserved for forms and API routes.
Token usage rules
Components consume semantic tokens (--st-*), never raw color values or primitives (--pt-*). Raw literals are permitted only in approved theme definition files (src/core/styles/ and src/site/styles/).
Tailwind v4 syntax
- Parenthesis shorthand for token references:
bg-(--st-color-surface-soft) - Type hints for ambiguous utilities:
text-(color:--st-color-text-muted)(distinguishes text color from text size) - Multi-variant components use
--_*scoped CSS variables, bound per variant in<style>and consumed by utilities in markup
Text sizing defaults
This project re-adds sensible typographic defaults for body text, headings, lists, and other elements after Tailwind’s Preflight reset. Most elements already have an appropriate font size without any utility class. Only add text-size utilities (e.g., text-sm, text-(length:--pt-text-lg)) when you need to override the default — not to restate it. Adding redundant size classes creates maintenance burden and fights the cascade.
Z-index layers
Components that participate in stacking must use the semantic layer contract (--st-layer-* tokens or their bridge utilities like z-dropdown, z-modal-surface). Do not set z-index with raw numeric values, direct primitive --pt-z-* references, or local arithmetic. The complete layer inventory lives in src/core/styles/semantic.css. See Design Tokens — Z-Index Layer Tokens for the naming convention and Component Authoring — Layered UI Contexts for authoring guidance.
What not to do
- Do not use
@apply. It is banned project-wide. - Do not reference
--pt-*primitives in component markup or styles. - Do not use hardcoded color values (
#fff,rgb(...), etc.) in components. - Do not add text-size utilities that merely restate the element’s default size.
- Do not set z-index with raw numeric values or local
calc()arithmetic — use the semantic layer contract.
Vanilla JS conventions
Interactive components use self-contained vanilla TypeScript with data-slot/data-state attributes, WeakMap instance storage, and astro:page-load lifecycle hooks. See the Vanilla JS Interactivity Patterns guide for full conventions.
Astro template rules
- Do not wrap
<script>tags inside Astro template expressions ({condition && <script>...</script>}). Use an unconditional<script>with a runtime DOM guard instead. - Do not add custom
data-*attributes to Astro<script>tags — attributes forceis:inlinemode, which disables TypeScript processing.
Component styling
Tailwind utilities are the primary styling mechanism. The approach layers several patterns:
LayoutSection wrapping
Section components wrap in LayoutSection for consistent vertical spacing and background treatment. Do not apply section-level spacing or background directly — let the layout primitive handle it.
BEM classes as CSS API hooks
BEM-style classes (e.g., .hero__title, .card__body) are retained alongside Tailwind utilities. They serve as stable CSS API hooks that site-level overrides can target in src/site/styles/components.css.
Scoped <style> blocks
Scoped <style> in Astro components is reserved for cases where Tailwind utilities are insufficient:
- Compound or complex CSS selectors
- Variant variable binding (
--_*scoped variables) data-statevisibility overrides and JS-created element styling (:global())
Focus ring pattern
The canonical focus ring for interactive elements:
focus-visible:outline-width-focus focus-visible:outline-focus-ring focus-visible:outline-offset-focus focus-visible:rounded-sm
Apply this pattern consistently to all focusable elements — buttons, links, inputs, and custom interactive widgets.
Accessibility baseline
The project targets WCAG 2.2 AA compliance:
- Semantic HTML first — use
<nav>,<main>,<article>,<button>, etc. Use ARIA only when semantic elements are insufficient. - Every interactive element must be keyboard-operable with a visible focus state.
- Use project primitives (
Heading,Button,Icon,Link) over raw HTML elements. These components enforce consistent accessibility patterns. - Maintain adequate color contrast ratios in both light and dark modes.
- Provide meaningful alt text for images and aria labels for icon-only buttons.
PageGridLayout conventions
PageGridLayout is the primary page layout component. Slot usage follows specific rules:
slot="subheader"— Hero sections (including compact page-title bands). This slot spans full width below the site header.slot="main-header"— Content metadata, filters, or elements that sit directly above<main>within the content column. Not for page titles.- Sidebar slot — Navigation, table of contents, or contextual links.
Markdown heading baseline
For routed content collections (pages, articles, docs):
- The frontmatter
title(orheroTitleoverride) renders as the page H1. - Do not use
#headings in body content. Start at##. #inside fenced code blocks is permitted for examples and comments.
Charter traceability
Every implementation that maps to a charter decision cites it with (§N) notation:
- In code: JSDoc comments reference the charter section (e.g.,
/** Token bridge */) - In docs and plans: Section headings or inline references (e.g., “Component styling”)
This keeps implementation traceable to the source decision in .docs/charter.md.
Commit and validation workflow
- Run validation before committing: Execute
npm run validateto run the full quality gate (format check, lint, token lint, content ingestion lint, type checking, build, accessibility checks). - Type checking mid-edit:
npx astro checkis safe to run at any point during development. - Commit messages: Follow
.docs/standards/GIT.mdfor the required format — structured body withWhat Changed:andWhy:sections. - Production builds: Run
npm run buildat implementation checkpoints to verify the full build succeeds.
Performance
- Zero-JS by default. Vanilla TypeScript only for interactive islands that genuinely require client-side behavior (see Hydration policy).
- Lazy-load images below the fold. Above-the-fold images use eager loading.
- SSG-first. Server-side rendering is reserved for dynamic routes (API endpoints, form handlers). Static generation handles everything else.
- Build-time processing over runtime computation. Content transforms, token generation, and data shaping happen at build time.
Anti-patterns to avoid
| Anti-pattern | Why it’s wrong |
|---|---|
| Raw color values in component styles | Bypasses the token system; breaks theme mode switching and brand consistency. |
@apply in stylesheets | Prohibited by project convention. Use Tailwind utilities in markup. |
Colon-prefixed attribute shorthands (:attr) in Astro template expressions | Breaks Prettier’s Astro parser. Use full-form attributes. |
Custom data-* attributes on <script> tags | Forces is:inline mode, which disables TypeScript processing. |
Wrapping <script> in Astro template expressions | Breaks Prettier. Use unconditional <script> with a runtime DOM guard. |
| Skipping the Heading component for headings | Loses consistent heading styles and the level-based size cascade. |
| Installing external component libraries as npm dependencies | The project adapts patterns, not packages. External deps add bundle weight and coupling. |
Design principles
The project follows these core principles consistently:
- KISS — Prefer the simplest correct solution. Complexity must justify itself.
- DRY — Factor out repeated logic into shared utilities or components.
- YAGNI — Don’t build for hypothetical requirements. Build what is needed now.
- SoC — Separate concerns between layers: core vs site, schema vs instance, layout vs content.
- SRP — One responsibility per module. If a component does two things, split it.