원문: https://github.com/facebook/astryx/wiki/Architecture-Cheat-Sheet · 번역 기준: 2026-09-03

감사(audit) rubric이 아니라 운영용 라우팅 가이드입니다. origin/mainbaa01b1c25355a85f26c7363f5c98b4d6e806c6b (2026-08-25) 기준으로 검증되었습니다. 링크된 owner API를 사용하고, 상세 점검은 기존 문서로 라우팅하세요.

Golden template: 단순한 컴포넌트

NewComponentnew-component의 이름을 바꾸고, 컴포넌트의 시맨틱에 맞는 intrinsic root를 선택하세요.

// 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';

이 순서는 현재의 Badge 패턴입니다: themeProps → 하나의 stylex.props(base, xstyle) 호출 → consumer className → consumer style → 중립적인 ...rest. 컴포넌트가 role, ARIA 값, 또는 handler를 소유한다면 아래 Public DOM/style/event/ref composition의 충돌 규칙을 적용하세요.

해당되는 것만 선택하세요

트리거 이 owner API를 조합
상태 전환을 announce useAnnounceuseTranslator; A6/A7/A16 감사
focus를 trap/복원 useFocusTrap; layer 참여자는 dismissal에도 함께 참여
인터랙티브 표면을 확장 useClickableContainer; input chrome은 useInputContainer 사용
hover/focus/touch 시 컨트롤을 표시 useContainerReveal
내비게이션을 렌더링 useLinkComponentLink / Item이 사용하는 공유 target/rel 처리
컬렉션을 탐색 ItemuseListFocus / useGridFocus / useTreeFocus; 필요하면 useTypeahead 추가
floating/modal UI를 열기 기존 Dialog/Popover/Tooltip 계열 우선; 그렇지 않으면 useLayer • dismissal/depth owner
크기를 받거나 제공 useSize; 컨테이너 owner는 SizeProvider 사용
폼 데이터를 수집 Field anatomy + getInputARIAuseInputContainer
컨테이너 geometry를 게시하거나 소비 container()와 4개의 방향별 padding 변수; edge owner는 edgeCompSlot 사용

Tier 1 — 해당되면 항상 점검

Layer protocol suite

기존 layer 계열을 우선하세요: 이미 portal 배치, dismissal, 중첩, focus, 라이프사이클을 소유하고 있습니다. useLayer는 저수준의 컴포넌트 저자용 인프라이지, consumer의 출발점이 아닙니다.

import {Button, Popover} from '@astryxdesign/core';

<Popover label="Settings" content={<div>Settings</div>}>
  <Button label="Settings" />
</Popover>

라우팅: packages/core/src/Layer/; Component Audit Rubric §1 and Q10.

Field/input anatomy

Consumer는 완성된 input과 그 표준 controlled API로 시작합니다. isRequired 또는 isOptional 중 하나를 선택하세요; TextInputisDisabled, isReadOnly, isLoading도 지원합니다—busy prop은 없습니다.

import {TextInput, type TextInputStatus} from '@astryxdesign/core/TextInput';

const status: TextInputStatus | undefined = isInvalid
  ? {type: 'error', message: validationMessage}
  : undefined;

<TextInput
  label="Email"
  description="Used for account notifications"
  value={email}
  onChange={setEmail}
  changeAction={saveEmail}
  status={status}
  isRequired
  isDisabled={isDisabled}
  disabledMessage={disabledMessage}
  isReadOnly={isReadOnly}
  isLoading={isSaving}
  hasClear
/>

시스템 input을 만들 때는 anatomy를 다시 만들지 말고 공개된 author primitive를 조합하세요:

import {Field} from '@astryxdesign/core/Field';
import {getInputARIA} from '@astryxdesign/core/utils';

const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(labelID, [
  description ? descriptionID : null,
]);

<Field label={label} inputID={inputID} labelID={labelID}
  description={description}
  descriptionID={description ? descriptionID : undefined}>
  <input id={inputID} aria-labelledby={ariaLabelledBy}
    aria-describedby={ariaDescribedBy} />
</Field>

라우팅: API Conventions — Input Component Props 및 감사 §§1, 3, 4.

Theming / visual state