Skip to content

Component Build Protocol

Cindy Zhang edited this page Jun 23, 2026 · 1 revision

Component Build Protocol

New here? Start with Component Lifecycle for the full end-to-end guide. This page covers the build phase in detail.

The build loop takes an approved spec (from the Component Specification Protocol) and produces a merged PR. The spec is the contract — deviations need justification.


Phase 1: Setup

  • Read the spec thoroughly — it's the contract
  • Check for dependencies (does this need another component first?)
  • Create worktree and feature branch
  • Reference existing components in the same family for patterns to follow

Phase 2: Foundation

Build the structural skeleton first:

  • Types and interfaces (from the spec's API section)
  • Core component file with proper JSDoc header
  • Hook(s) if the component has reusable behavior (e.g., usePopover)
  • Wire up to the package index export

Phase 3: System Integration Check

Before implementation, verify the component plugs into system infrastructure:

  • Does it use Dialog for modal/overlay patterns? (not raw portals)
  • Does it use Layout inside dialogs for consistent structure?
  • Do styles actually apply without clobbering composed components?
  • Does it participate in the theming layer correctly?

Phase 4: Implementation

Build the component following these principles:

Composition

  • Compose from existing Astryx components — never rebuild what exists
  • Everything must go through the theming/swizzle layer — no raw <button>, <hr>, <svg>
  • Icons from IconRegistry — hardcoded SVGs aren't themable
  • Slots are passthrough, not wrappers — parent renders slot content as-is
  • Don't hoist child props onto the parent

Styling

  • All :hover styles MUST use @media (hover: hover) guards. :active stays unguarded.
  • Use elevation token variables for shadows — never raw boxShadow strings
  • Use spacing tokens (spacingVars) — never raw px values
  • Interactive states use --radius-content for rounded corners
  • Think about the theming story for every CSS custom property

State & Behavior

  • Push state sync into hooks, not useEffect
  • Never remove interactive elements from the DOM on state change — hide visually
  • Don't imperatively attach event listeners to child DOM elements — use React's event system
  • Components implementing ARIA patterns should find the correct semantic element for handlers

API

  • Follow the spec's API exactly. Deviations need justification.
  • Reuse existing hooks (useListFocus, usePopover, etc.)
  • Debounce should be configurable, not hardcoded

Phase 5: Status States

If the component is an input or has validation:

  • Error/warning/success must have visual border treatment on the input wrapper
  • Status states need storybook examples for each variant

Phase 6: Stories

Write Storybook stories that:

  • Demonstrate the differences between options, not just enumerate them
  • Use containers and content that make visual distinctions obvious
  • Show composition patterns (inside Dialog, Table, Card, AppShell)
  • Include stories for all status states if applicable

Phase 7: Tests

  • Unit tests for behavior and edge cases
  • Test keyboard interactions
  • Test ARIA attributes
  • Test hook behavior independently if applicable

Phase 8: Documentation

  • Typed .doc.mjs file following the ComponentDoc type (see Component Authoring Guide)
  • File header with structured JSDoc (@input, @output, @position)
  • Update parent docs if architecture changes

Phase 9: PR

  • Create PR linking to the spec issue
  • Run pnpm test and pnpm lint — all must pass
  • Do NOT auto-merge — wait for human review
  • PR touches packages/core/src/? Vibe test may be needed (CI checks this)

Phase 10: Self-Review

After the PR is created, post a review using this checklist. This leaves a paper trail for the reviewer.

  1. Compose, don't rebuild — any system components reimplemented as raw HTML?
  2. Everything through theming/swizzle — any raw <button>, <hr>, <svg>?
  3. Icons from registry — any hardcoded SVGs? Use getIcon() from globalIconRegistry.
  4. xdsThemeProps on interactive roots — every component root element that consumers might style via @scope needs xdsThemeProps() via mergeProps.
  5. No wrapper divs — handlers/refs go on the interactive element itself (the button, the link), not a wrapping div. Extra DOM breaks flex layouts and complicates styling.
  6. :hover guards — all :hover in @media (hover: hover)?
  7. Elevation tokens — any raw boxShadow strings?
  8. Spacing tokens — any raw px values that should be spacingVars?
  9. No CSS shorthands — use flexGrow/flexShrink/flexBasis instead of flex. StyleX works better with explicit properties.
  10. ARIA patterns — established patterns followed? Roles correct? Switching primitives (e.g. useLayerusePopover) may change ARIA attributes — check tests.
  11. No useEffect smells — state sync in hooks, not useEffect?
  12. Slot consistency — slots are passthrough ReactNode, not wrappers?
  13. Family alignment — API matches sibling components in the same family?
  14. Astryx naming conventions — onChange, variant, label, XStyle capitalization?
  15. No premature flexibility — escape hatches without real use cases?
  16. Extract repeated structures — repeated optional blocks → named sub-components?
  17. Reuse existing hooks — using useListFocus, usePopover etc.?
  18. Status states — error/warning/success have visual border treatment?
  19. Stories demonstrate differences — not just enumerating options?
  20. Theming story — what happens if a theme changes values this component depends on?
  21. Don't label defaults as "legacy" — if it's the standard path, it's primary
  22. Share implementation — default and explicit paths use the same underlying code?
  23. Spec compliance — does the implementation match the spec? Any deviations justified?
  24. Surface area — do exports match the spec? Any duplicates of existing components?
  25. System integration — overlay uses Dialog/Layer? Layout inside Dialog? Styles not clobbering?
  26. Standalone extractions — anything useful outside this family that should be its own component?
  27. Tests pass locallypnpm test before pushing. Don't rely on CI for first-pass validation.

Quick Self-Review (for refactors and updates)

When updating existing components (not building from scratch), use this lighter checklist:

  1. Icons from registry — replaced any hardcoded SVGs with getIcon()?
  2. xdsThemeProps present — new interactive elements have xdsThemeProps() via mergeProps?
  3. No wrapper divs — handlers go directly on the interactive element?
  4. No CSS shorthands — explicit flexGrow/flexShrink/flexBasis, not flex?
  5. ARIA still correct — changing primitives (hooks, layers) may change ARIA attributes. Check tests.
  6. Tests pass locally — run the test file before pushing.
  7. Sandbox/stories updated — if you removed a prop, removed it from all callsites?
  8. Popover backgroundusePopover/useLayer popovers need backgroundColor: 'transparent' on the layer xstyle to avoid UA default canvas background.

Phase 11: Review Response

  • Address review feedback
  • Add new review feedback patterns to the protocol for future reference
  • If feedback reveals a spec issue, update the spec too

Key Principles (ranked by impact)

  1. Compose, don't rebuild — use existing Astryx components, don't reimplement
  2. Audit surface area — every proposed export must justify its existence against existing components
  3. System integration is non-negotiable — overlays use Dialog, layouts use Layout, styles don't clobber
  4. Slots are passthrough — parent renders slot content as-is, never wraps it
  5. Everything through theming/swizzle — no raw HTML elements that bypass the system
  6. Positional > semantic for flexible slotsendContent not action
  7. Natural language priors are strong — work with them, not against them
  8. Vibe test when unsure — override rate is the cleanest signal
  9. Internal architecture ≠ OSS architecture — port the value, not the structure

Clone this wiki locally