-
Notifications
You must be signed in to change notification settings - Fork 27
Component Build Protocol
Cindy Zhang edited this page Jun 23, 2026
·
1 revision
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.
- 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
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
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?
Build the component following these principles:
- 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
- All
:hoverstyles MUST use@media (hover: hover)guards.:activestays unguarded. - Use elevation token variables for shadows — never raw
boxShadowstrings - Use spacing tokens (
spacingVars) — never raw px values - Interactive states use
--radius-contentfor rounded corners - Think about the theming story for every CSS custom property
- 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
- Follow the spec's API exactly. Deviations need justification.
- Reuse existing hooks (useListFocus, usePopover, etc.)
- Debounce should be configurable, not hardcoded
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
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
- Unit tests for behavior and edge cases
- Test keyboard interactions
- Test ARIA attributes
- Test hook behavior independently if applicable
- Typed
.doc.mjsfile following theComponentDoctype (see Component Authoring Guide) - File header with structured JSDoc (
@input,@output,@position) - Update parent docs if architecture changes
- Create PR linking to the spec issue
- Run
pnpm testandpnpm 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)
After the PR is created, post a review using this checklist. This leaves a paper trail for the reviewer.
- Compose, don't rebuild — any system components reimplemented as raw HTML?
-
Everything through theming/swizzle — any raw
<button>,<hr>,<svg>? -
Icons from registry — any hardcoded SVGs? Use
getIcon()fromglobalIconRegistry. -
xdsThemeProps on interactive roots — every component root element that consumers might style via
@scopeneedsxdsThemeProps()viamergeProps. - 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.
-
:hover guards — all
:hoverin@media (hover: hover)? -
Elevation tokens — any raw
boxShadowstrings? -
Spacing tokens — any raw px values that should be
spacingVars? -
No CSS shorthands — use
flexGrow/flexShrink/flexBasisinstead offlex. StyleX works better with explicit properties. -
ARIA patterns — established patterns followed? Roles correct? Switching primitives (e.g.
useLayer→usePopover) may change ARIA attributes — check tests. - No useEffect smells — state sync in hooks, not useEffect?
- Slot consistency — slots are passthrough ReactNode, not wrappers?
- Family alignment — API matches sibling components in the same family?
- Astryx naming conventions — onChange, variant, label, XStyle capitalization?
- No premature flexibility — escape hatches without real use cases?
- Extract repeated structures — repeated optional blocks → named sub-components?
- Reuse existing hooks — using useListFocus, usePopover etc.?
- Status states — error/warning/success have visual border treatment?
- Stories demonstrate differences — not just enumerating options?
- Theming story — what happens if a theme changes values this component depends on?
- Don't label defaults as "legacy" — if it's the standard path, it's primary
- Share implementation — default and explicit paths use the same underlying code?
- Spec compliance — does the implementation match the spec? Any deviations justified?
- Surface area — do exports match the spec? Any duplicates of existing components?
- System integration — overlay uses Dialog/Layer? Layout inside Dialog? Styles not clobbering?
- Standalone extractions — anything useful outside this family that should be its own component?
-
Tests pass locally —
pnpm testbefore pushing. Don't rely on CI for first-pass validation.
When updating existing components (not building from scratch), use this lighter checklist:
-
Icons from registry — replaced any hardcoded SVGs with
getIcon()? -
xdsThemeProps present — new interactive elements have
xdsThemeProps()viamergeProps? - No wrapper divs — handlers go directly on the interactive element?
-
No CSS shorthands — explicit
flexGrow/flexShrink/flexBasis, notflex? - ARIA still correct — changing primitives (hooks, layers) may change ARIA attributes. Check tests.
- Tests pass locally — run the test file before pushing.
- Sandbox/stories updated — if you removed a prop, removed it from all callsites?
-
Popover background —
usePopover/useLayerpopovers needbackgroundColor: 'transparent'on the layer xstyle to avoid UA default canvas background.
- Address review feedback
- Add new review feedback patterns to the protocol for future reference
- If feedback reveals a spec issue, update the spec too
- Compose, don't rebuild — use existing Astryx components, don't reimplement
- Audit surface area — every proposed export must justify its existence against existing components
- System integration is non-negotiable — overlays use Dialog, layouts use Layout, styles don't clobber
- Slots are passthrough — parent renders slot content as-is, never wraps it
- Everything through theming/swizzle — no raw HTML elements that bypass the system
-
Positional > semantic for flexible slots —
endContentnotaction - Natural language priors are strong — work with them, not against them
- Vibe test when unsure — override rate is the cleanest signal
- Internal architecture ≠ OSS architecture — port the value, not the structure