-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Architecture Cheat Sheet
Operational routing guide, not an audit rubric. Verified against
origin/mainate3259c94696e9730d11d4c97e0adca5af1b14cf3(2026-08-25). Use the linked owner API and route detailed checks to the existing docs.
Rename NewComponent and new-component; choose the intrinsic root that matches the component's semantics.
// Copyright (c) Meta Platforms, Inc. and affiliates.
/**
* @file NewComponent.tsx
* @input React content, BaseProps DOM passthrough, StyleX overrides
* @output Exports NewComponent and NewComponentProps
* @position Core leaf component; consumed by index.ts
*
* SYNC: When modified, update these files to stay in sync:
* - /packages/core/src/NewComponent/NewComponent.doc.mjs
* - /packages/core/src/NewComponent/NewComponent.test.tsx
* - /packages/core/src/NewComponent/index.ts
* - /apps/storybook/stories/NewComponent.stories.tsx
*/
import type {ReactNode} from 'react';
import * as stylex from '@stylexjs/stylex';
import type {BaseProps} from '../BaseProps';
import {colorVars, spacingVars} from '../theme/tokens.stylex';
import {mergeProps, themeProps} from '../utils';
const styles = stylex.create({
root: {
alignItems: 'center',
color: colorVars['--color-text-primary'],
display: 'inline-flex',
gap: spacingVars['--spacing-1'],
},
});
export interface NewComponentProps extends BaseProps<HTMLSpanElement> {
children: ReactNode;
ref?: React.Ref<HTMLSpanElement>;
}
export function NewComponent({
children,
xstyle,
className,
style,
ref,
...rest
}: NewComponentProps) {
return (
<span
ref={ref}
{...mergeProps(
themeProps('new-component'),
stylex.props(styles.root, xstyle),
className,
style,
)}
{...rest}>
{children}
</span>
);
}
NewComponent.displayName = 'NewComponent';This order is the current Badge pattern: themeProps → one stylex.props(base, xstyle) call → consumer className → consumer style → neutral ...rest. If the component owns a role, ARIA value, or handler, apply the collision rules in Public DOM/style/event/ref composition below.
| Trigger | Compose this owner API |
|---|---|
| Announce a state transition |
useAnnounce + useTranslator; audit A6/A7/A16 |
| Trap/restore focus |
useFocusTrap; layer participants also join dismissal |
| Enlarge an interactive surface |
useClickableContainer; input chrome uses useInputContainer
|
| Reveal controls on hover/focus/touch | useContainerReveal |
| Render navigation |
useLinkComponent + the shared target/rel handling used by Link / Item
|
| Navigate a collection |
Item + useListFocus / useGridFocus / useTreeFocus; add useTypeahead when required |
| Open floating/modal UI | Existing Dialog/Popover/Tooltip family first; otherwise useLayer + dismissal/depth owners |
| Accept or provide size |
useSize; container owners use SizeProvider
|
| Collect form data |
Field anatomy + getInputARIA + useInputContainer
|
| Publish or consume container geometry |
container() and the four directional padding variables; edge owners use edgeCompSlot
|
Floating and modal UI shares one protocol for host/portal placement, anchoring, dismissal ordering, nesting depth, focus, and lifecycle. Prefer an existing layer family; a new primitive must join all applicable Layer owners rather than rebuilding one concern locally.
import {useLayer, useLayerDismissal, LayerDepthProvider} from '@astryxdesign/core/Layer';
import {useFocusTrap} from '@astryxdesign/core/hooks';Route: packages/core/src/Layer/; Component Audit Rubric §1 and Q10.
A form control uses the shared label, description, required/optional, status, clear-control, group-ID, and input-chrome contracts. Do not assemble a private field wrapper.
import {
Field,
FieldLabel,
FieldStatus,
InputClearButton,
inputWrapperStyles,
} from '@astryxdesign/core/Field';
import {useInputContainer} from '@astryxdesign/core/hooks';
import {getInputARIA} from '@astryxdesign/core/utils';Route: API Conventions — Input Component Props and audit §§1, 3, 4.
Paint with semantic tokens, put themeProps only on stable painting elements, and reflect style-driving props/state through that target. Theme targets are public API; structural wrappers stay private.
<span
{...mergeProps(
themeProps('component-name', {
variant,
disabled: isDisabled ? 'disabled' : null,
}),
stylex.props(styles.root, variants[variant], xstyle),
className,
style,
)}
/>Route: Theming Infrastructure — Component Theming and audit §2.
Compose the shared primitive for the interaction class. useAnnounce owns transient announcements; VisuallyHidden owns persistent hidden content—it is not a replacement live-region implementation.
import {
useAnnounce,
useClickableContainer,
useContainerReveal,
useFocusTrap,
useInputContainer,
} from '@astryxdesign/core/hooks';
import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
const announce = useAnnounce();
// In the real transition handler: announce(translatedMessage, 'polite');Route: audit A6/A7/A16/A17 and the component/hook docs emitted by astryx component / astryx docs.
Use one semantic/focus owner per row, Item anatomy where it fits, the focus hook matching the collection shape, and shared Indicator primitives. Do not add a local arrow-key switch or private selection row.
import {Item} from '@astryxdesign/core/Item';
import {
useGridFocus,
useListFocus,
useTreeFocus,
useTypeahead,
} from '@astryxdesign/core/hooks';
import {CheckIndicator, useIndicator} from '@astryxdesign/core/Indicator';Route: packages/core/src/Item/, hooks/use*Focus.ts, Indicator/; audit A17.
Containers publish the padding they actually paint; bleed consumers read the matching logical edge with a 0px fallback, and nested container owners reset then republish. Use the owner utility instead of hardcoded negative spacing.
import {container, edgeCompSlot, EDGE_COMP_ATTR} from '@astryxdesign/core/Layout';
const containerVars = [
'--container-padding-inline-start',
'--container-padding-inline-end',
'--container-padding-block-start',
'--container-padding-block-end',
] as const;Route: Layout/container.stylex.ts, Layout/padding.stylex.ts, and Container Padding System.
One owner supplies the whole utterance or the semantic values needed to translate it. Visible and assistive text share the catalog/provider locale; direction comes from the i18n system and CSS logical properties.
import {useDirection, useTranslator} from '@astryxdesign/core/i18n';
import {useAnnounce} from '@astryxdesign/core/hooks';
const t = useTranslator();
const direction = useDirection();Route: audit §9 plus A7/A16; API Conventions.
Every link-capable component resolves explicit as → LinkProvider → native anchor and preserves router props/ref plus safe target/rel handling. Never hardcode <a> for a public navigation path.
import {
LinkProvider,
useLinkComponent,
type LinkComponentType,
} from '@astryxdesign/core/Link';
const LinkComponent = useLinkComponent(as);Route: packages/core/src/Link/ and API Conventions — Use the System.
BaseProps, the React 19 ref prop, mergeProps, and composeEventHandlers preserve the public DOM contract. Combine styles, forward neutral props, place component-owned semantics after rest, and compose collisions explicitly—normally consumer first so preventDefault() cancels built-in behavior.
<button
ref={ref}
{...mergeProps(themeProps('choice'), stylex.props(styles.root, xstyle), className, style)}
{...rest}
role="radio"
aria-checked={isSelected}
onClick={composeEventHandlers(onClickProp, handleSelect)}
/>Route: API Conventions — BaseProps and precedence and audit P2/P15.
When a component accepts size, resolve explicit prop → inherited context → default. A grouping component that owns density provides the resolved size to eligible descendants.
import {SizeProvider, useSize} from '@astryxdesign/core/SizeContext';
const size = useSize(sizeProp, 'md');
// <SizeProvider value={size}>{children}</SizeProvider>Route: SizeContext/SizeContext.ts; API Conventions — Size Variants.
Component-owned glyphs resolve by semantic name through the Icon registry; shared selection/control visuals resolve through Indicator. Use a narrow content slot only for consumer-owned content, not as a component-replacement registry.
import {Icon, useIcon} from '@astryxdesign/core/Icon';
import {CheckIndicator, RadioIndicator, useIndicator} from '@astryxdesign/core/Indicator';Route: packages/core/src/Icon/, Indicator/; audit Q4/T17/I10.
When adding status, use the canonical feedback values info | success | warning | error. Input validation intentionally uses the subset success | warning | error; busy / loading is operational state, not another status. Field-like controls use Field status anatomy; general feedback uses Banner; transition announcements use translated text through useAnnounce.
import type {BannerStatus} from '@astryxdesign/core/Banner';
import type {InputStatus, InputStatusType} from '@astryxdesign/core/Field';
import {useAnnounce} from '@astryxdesign/core/hooks';
import {useTranslator} from '@astryxdesign/core/i18n';
const t = useTranslator();
const announce = useAnnounce();
const feedback: BannerStatus = 'info';
const validationType: InputStatusType = 'error';
const validation: InputStatus = {
type: validationType,
// Placeholder: add this key to your component's catalog before use.
message: t('@astryx.yourComponent.invalid'),
};
// In the transition handler: announce(validation.message ?? '', 'polite');Do not introduce parallel values such as danger, critical, positive, negative, severity, or tone. Components select the status; the status system owns the matching surface, text, border, and icon token family.
Route: API Conventions — Status / Validation, audit field behavior plus A3/A7/A11/A12/A16/I1, and Theming Infrastructure for semantic token roles.
- React first: escape to the DOM only for a named performance/accessibility requirement; never read data React already knows.
- Reuse contracts: compose the existing component or owner primitive instead of rebuilding its behavior and appearance.
-
Data attributes are rare external contracts: never use them for component communication; theming state flows through
themeProps.
Start here Astryx Philosophy Contributing with AI Assistants Contributing
Architecture System Architecture Architecture Cheat Sheet Theming Infrastructure Distribution
Building a component Component Lifecycle Component Authoring Guide API Conventions Design Conventions
Quality Component Audit Rubric Accessibility Checklist
Operations Release Process Night Watch Overview