# Selector Contract

`@orioncactuscorp/ui` 컴포넌트를 consumer CSS에서 커스터마이즈할 때 사용하는 안정 selector 계약입니다. CSS Module 해시 클래스는 빌드마다 바뀌므로 selector로 사용하지 않고, 컴포넌트가 노출하는 data attribute를 사용합니다.

이 문서의 selector·slot 표는 계약이며, 변경·제거 규칙은 [Stability Policy](./stability.md)를 따릅니다.

## 커스터마이즈 selector 3계층

| 계층          | 형태                                                                                                                  | 용도                               |
| ------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| 컴포넌트 루트 | `data-oc-component="<name>"`                                                                                          | 독립 시각 컴포넌트/프리미티브 식별 |
| 내부 slot     | `data-oc-part="<slot>"`                                                                                               | 컴포넌트 내부의 안정 slot          |
| 시각/상태     | `data-oc-size`, `data-oc-variant`, `data-oc-appearance`, `data-oc-state`, `data-oc-invalid`, `data-oc-orientation` 등 | 컴포넌트가 소유한 시각·상태 분기   |

값은 kebab-case입니다.

```css
/* 예: invalid 상태의 TextInput content 표면 커스터마이즈 */
[data-oc-component='text-input'][data-oc-invalid='true']
  [data-oc-part='content'] {
  --oc-text-input-border-color: var(--oc-color-theme-status-negative);
}
```

다음은 selector로 사용하지 마세요.

- `@orioncactuscorp/ui`의 CSS Module 해시 클래스
- 아래 [Legacy 호환 출력](#legacy-호환-출력)에 있는 구형 attribute
- 계약에 없는 incidental wrapper에 의존하는 DOM 깊이 selector

## Override가 동작하는 방식

- 컴포넌트 소스는 루트 modifier selector를 `:where(...)`로 낮은 specificity로 유지합니다. 나중에 로드되는 consumer 클래스 하나로 루트 레벨 `--oc-*` component contract 변수를 덮을 수 있습니다.
- 스타일 override는 paint property를 직접 재선언하기보다 컴포넌트가 노출하는 contract 변수(`--oc-{component}-*`)를 덮는 방식을 우선하세요. 상태 전이·모션과의 일관성이 유지됩니다.
- 방향성 계약(`--oc-*` 변수, `data-oc-*` 값, prop)은 물리 방향(top/right/bottom/left)이 아니라 논리 방향(`block-start`, `inline-end`, `start`, `end` 등)을 사용합니다. RTL에서 자동으로 뒤집힙니다.

## Interaction 프리미티브

`Interaction`은 hover/press/focus 피드백을 그리는 passive 시각 레이어입니다. `pointer-events: none`이고 자기 상태를 갖지 않으며, 부모 컴포넌트가 자기 상태 selector로 자식 Interaction의 opacity를 제어합니다.

```scss
.myControl {
  &:focus-visible > [data-oc-component='interaction'] {
    opacity: var(--oc-interaction-opacity-focus);
  }

  @media (hover: hover) {
    &:hover:not(:disabled) > [data-oc-component='interaction'] {
      opacity: var(--oc-interaction-opacity-hover);
    }
  }
}
```

variant(`light`/`normal`/`strong`)는 `--oc-interaction-opacity-*` 토큰 값만 바꿉니다.

## Badge 계약

| Component | Root selector               | Visual selectors                                                                                          | Stable parts |
| --------- | --------------------------- | --------------------------------------------------------------------------------------------------------- | ------------ |
| `Badge`   | `data-oc-component="badge"` | `data-oc-variant="normal\|accent\|overlay"`, `data-oc-size="xsmall\|small"`, `data-oc-interactive="true"` | `content`    |

`Badge`의 작은 named size는 `data-oc-size="xsmall"`을 사용합니다. 이전
`data-oc-size="tiny"` 값은 major 변경으로 제거됐습니다.

## Field 레이아웃 계약

`Field`는 label/control/message compound 구조를 유지한 채 vertical/horizontal orientation을 노출합니다.

| Component      | Root selector                       | Orientation selector                         | Part selector            | Stable parts                      |
| -------------- | ----------------------------------- | -------------------------------------------- | ------------------------ | --------------------------------- |
| `Field`        | `data-oc-component="field"`         | `data-oc-orientation="vertical\|horizontal"` | -                        | `field-control`, message 컴포넌트 |
| `FieldControl` | `data-oc-component="field-control"` | -                                            | `data-oc-part="control"` | 렌더링된 control 컴포넌트를 감쌈  |

- horizontal label 폭은 `--oc-field-label-inline-size`로 제어합니다. `labelWidth` prop의 숫자 값은 `16px = 1rem` 기준 rem으로 변환됩니다.
- `form`/`fieldset` 같은 상위 요소에 같은 변수를 지정하면 여러 Field가 label 폭을 공유합니다.
- `--oc-field-label-max-inline-size`는 label 컬럼 상한이며 기본값은 `35%`입니다.
- horizontal의 helper/error message는 control 컬럼에 배치되어 input 시작선에 정렬됩니다.
- multi-control row는 `Field` layout 책임이 아닙니다. 내부 control group을 `FieldControl` 안에서 조합하거나, 하나의 label이 여러 native control을 설명해야 하면 시맨틱 grouping 프리미티브를 사용하세요.

## Field Control slot 계약

| Component   | Root selector                    | Native part               | Content surface part     | Slot parts                                                  |
| ----------- | -------------------------------- | ------------------------- | ------------------------ | ----------------------------------------------------------- |
| `TextInput` | `data-oc-component="text-input"` | `data-oc-part="input"`    | `data-oc-part="content"` | `leading-content`, `trailing-content`                       |
| `TextArea`  | `data-oc-component="text-area"`  | `data-oc-part="textarea"` | `data-oc-part="content"` | `supporting-content`, `leading-content`, `trailing-content` |
| `Select`    | `data-oc-component="select"`     | `data-oc-part="select"`   | `data-oc-part="content"` | `leading-content`, `trailing-content`, `indicator`          |

slot content 프리미티브는 독립 컴포넌트 selector를 노출합니다.

| Primitive          | Selector                                 | Variants                                       | 접근성 기본값                              |
| ------------------ | ---------------------------------------- | ---------------------------------------------- | ------------------------------------------ |
| `TextInputContent` | `data-oc-component="text-input-content"` | `data-oc-variant="text\|icon\|action\|custom"` | `icon` variant는 `aria-hidden="true"` 기본 |
| `TextAreaContent`  | `data-oc-component="text-area-content"`  | `data-oc-variant="text\|icon\|action\|custom"` | `icon` variant는 `aria-hidden="true"` 기본 |
| `SelectContent`    | `data-oc-component="select-content"`     | `data-oc-variant="text\|icon\|action\|custom"` | `icon` variant는 `aria-hidden="true"` 기본 |

`TextArea`는 `supporting-content`에 `data-oc-align`을 소유합니다.

| `data-oc-align` | 의미                         |
| --------------- | ---------------------------- |
| `start`         | leading content만 존재       |
| `end`           | trailing content만 존재      |
| `space-between` | leading과 trailing 모두 존재 |

## Cell slot 계약

| Component  | Root selector                   | Visual selectors                                                                                                            | Stable parts                                                     |
| ---------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `Cell`     | `data-oc-component="cell"`      | `data-oc-divider="normal\|neutral\|alternative\|none"`                                                                      | -                                                                |
| `CellItem` | `data-oc-component="cell-item"` | `data-oc-fill-width="true\|false"`, `data-oc-interactive="true"`, `data-oc-divider`, `data-oc-vertical-align="top\|center"` | `leading`, `content`, `text`, `label`, `description`, `trailing` |

- `fillWidth=true`는 row가 inline padding을 소유하고 interaction 레이어를 row 경계 안에 둡니다. `false`는 content를 flush로 두고 interaction 레이어만 inline margin으로 확장합니다.
- `verticalAlign=top`은 첫 content 줄 기준 정렬, `center`는 전체 content 높이 기준 중앙 정렬입니다.
- `interactive=true`일 때 row 활성화와 별개로 동작해야 하는 내부 control은 `data-oc-cell-control`로 opt-in한 경우에만 row 클릭이 활성화합니다.

## Runtime Motion 계약

runtime motion wrapper는 SCSS `oc-motion`과 같은 intent/phase/pace/reduced vocabulary를 사용하며, 시맨틱 상태와 진행 중 애니메이션 상태를 분리해 노출합니다.

| Component      | Root selector                       | Visual selectors                                                                                                 | Stable parts |
| -------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------ |
| `MotionExpand` | `data-oc-component="motion-expand"` | `data-oc-state="open\|closed"`, `data-oc-motion-intent`, `data-oc-motion-state="closed\|opening\|open\|closing"` | `content`    |

`prefers-reduced-motion: reduce`가 활성일 때 `data-oc-reduced-motion="true"`가 방출됩니다. reduced 상태의 `block-size` 같은 non-feedback property expand는 즉시 해소되며 mount/unmount presence 계약은 동일합니다.

## Accordion 계약

summary row는 Cell slot 계약을 시각적으로 따르되 trigger는 native button입니다. details expand는 CSS grid가 아니라 React runtime motion 레이어가 담당합니다.

| Component              | Root selector                               | Visual selectors                                                                                                                      | Stable parts                                                                                  |
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `Accordion`            | `data-oc-component="accordion"`             | `data-oc-divider="normal\|neutral\|alternative\|none"`, `data-oc-disabled="true"`, `data-oc-animation="disabled"`                     | -                                                                                             |
| `AccordionItem`        | `data-oc-component="accordion-item"`        | `data-oc-state="open\|closed"`, `data-oc-divider`, `data-oc-disabled="true"`                                                          | -                                                                                             |
| `AccordionSummary`     | `data-oc-component="accordion-summary"`     | `data-oc-state="open\|closed"`, `data-oc-fill-width="true\|false"`, `data-oc-vertical-align="top\|center"`, `data-oc-disabled="true"` | `header`, `leading`, `content`, `text`, `label`, `description`, `trailing`, `indicator-frame` |
| `AccordionDetails`     | `data-oc-component="accordion-details"`     | `data-oc-state="open\|closed"`, `data-oc-motion-intent`, `data-oc-motion-state`, `data-oc-reduced-motion="true"`                      | `panel-inner`                                                                                 |
| `AccordionDescription` | `data-oc-component="accordion-description"` | -                                                                                                                                     | -                                                                                             |
| `AccordionContent`     | `data-oc-component="accordion-content"`     | -                                                                                                                                     | -                                                                                             |

`AccordionSummary`가 `aria-expanded`/`aria-controls`/summary id를, `AccordionDetails`가 대응 id와 `aria-labelledby`를 소유합니다. 닫힌 force-mounted details는 `aria-hidden`과 `inert`로 접근성 트리·포커스에서 제외됩니다.

## Tabs 계약

Tabs는 Base UI의 선택·키보드·ARIA 연결을 사용하고, oc-ui가 size, layout, indicator, overflow 표현을 소유합니다.

| Component   | Root selector              | Visual selectors                                                                                           | Stable parts                        |
| ----------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `TabsRoot`  | `data-oc-component="tabs"` | -                                                                                                          | -                                   |
| `TabsList`  | `data-oc-part="list"`      | `data-oc-size="small\|medium\|large"`, `data-oc-full-width="true\|false"`, `data-oc-padding="true\|false"` | `scroller`, `indicator`, `trailing` |
| `TabsTab`   | `data-oc-part="tab"`       | `aria-selected="true\|false"`, `aria-disabled="true"`                                                      | `label`                             |
| `TabsPanel` | `data-oc-part="panel"`     | `hidden`                                                                                                   | -                                   |

`TabsTab`의 선택·disabled 스타일은 Base UI 런타임 attribute인 `data-active`/`data-disabled`가 아니라 `aria-selected`/`aria-disabled`를 기준으로 합니다. disabled tab은 `aria-disabled="true"`이지만 native `disabled`가 아니며 키보드 포커스를 받을 수 있습니다. `TabsPanel keepMounted`는 비활성 panel을 DOM에 유지하고 `hidden`으로 접근성 트리에서 제외합니다.

Base UI가 tab 관계와 키보드 탐색을 위해 생성하는 `role`, `tabIndex`, `aria-selected`, `aria-controls`, `aria-labelledby`, `aria-orientation`, panel `id`/`hidden`은 컴포넌트 소유 계약이며 소비자 prop으로 재정의할 수 없습니다.

Tab typography는 `TabsList`의 `size`에 따라 large는 `headline2/bold`, medium은 `body1/bold`, small은 `body2/bold`가 적용되고 각 `TabsTab`이 상속합니다. 소비자는 `TabsList`의 `className` 또는 `[data-oc-part='list']` selector에서 `font-size`, `line-height`, `letter-spacing`, `font-weight`를 재정의해 모든 tab item의 typography를 일괄 조정할 수 있습니다.

Tab content는 inline padding 없이 배치되고 배경색이 transparent인 self `Interaction`이 `--oc-tabs-tab-interaction-margin-inline`만큼 좌우 바깥으로 확장되어 실제 pointer hit area가 됩니다. 인접한 Interaction이 겹치지 않도록 기본 gap은 이 값의 2배이며 `padding` 상태와 무관하게 유지됩니다. `padding=false`이면 list 좌우 여백이 없고 양끝 Interaction의 바깥 확장은 list 경계에서 잘립니다. `padding=true`이면 `--oc-space-section-padding-viewport-x`가 기본 좌우 여백으로 적용됩니다.

다음 `--oc-tabs-*` 변수로 list와 tab의 spacing, 상태 색, indicator, overflow fade를 조정할 수 있습니다.

| Variable                                  | Default                                                                               | Scope                        |
| ----------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------- |
| `--oc-tabs-list-padding-inline`           | `0px`; `padding=true`이면 `--oc-space-section-padding-viewport-x`                     | list 좌우 여백               |
| `--oc-tabs-list-padding-block`            | `0px`                                                                                 | list 상하 여백               |
| `--oc-tabs-list-gap`                      | `--oc-tabs-tab-interaction-margin-inline`의 2배                                       | tab 사이 간격                |
| `--oc-tabs-tab-interaction-margin-inline` | `--oc-space-item-padding-xsmall`                                                      | 각 tab Interaction 좌우 확장 |
| `--oc-tabs-tab-padding-block`             | large/medium은 `--oc-space-item-padding-mini`, small은 `--oc-space-item-padding-tiny` | 각 tab 상하 클릭 영역        |
| `--oc-tabs-edge-fade-size`                | `3rem`                                                                                | overflow edge fade 크기      |
| `--oc-tabs-indicator-block-size`          | `0.125rem`                                                                            | indicator 두께               |
| `--oc-tabs-indicator-radius`              | `--oc-atomic-radius-max`                                                              | indicator 모서리 반경        |
| `--oc-tabs-indicator-color`               | `--oc-color-theme-label-strong`                                                       | 선택 indicator 색            |
| `--oc-tabs-indicator-disabled-color`      | `--oc-color-theme-label-disable`                                                      | disabled 선택 indicator 색   |
| `--oc-tabs-label-color`                   | `--oc-color-theme-label-assistive`                                                    | 기본 label 색                |
| `--oc-tabs-label-hover-color`             | `--oc-color-theme-label-alternative`                                                  | hover label 색               |
| `--oc-tabs-label-selected-color`          | `--oc-color-theme-label-strong`                                                       | 선택 label 색                |
| `--oc-tabs-label-disabled-color`          | `--oc-color-theme-label-disable`                                                      | disabled label 색            |

## Icon 계약

| Primitive | Selector                       | 접근성 기본값                                                                               |
| --------- | ------------------------------ | ------------------------------------------------------------------------------------------- |
| `SvgIcon` | `data-oc-component="svg-icon"` | 기본 `aria-hidden="true"`. 의미 있는 아이콘은 `role="img"` + `aria-label`/`aria-labelledby` |

## Loading placeholder 계약

상태 안내가 필요하면 `Loading`(assistive technology에 status announce), 부모 영역이 loading 시맨틱을 이미 소유하면 `Skeleton`(장식)을 사용합니다.

| Component  | Root selector                  | Visual selectors                                                                  | Stable parts |
| ---------- | ------------------------------ | --------------------------------------------------------------------------------- | ------------ |
| `Loading`  | `data-oc-component="loading"`  | `data-oc-variant="circular"`, `data-oc-size`                                      | `indicator`  |
| `Skeleton` | `data-oc-component="skeleton"` | `data-oc-variant="text\|rectangle\|circle"`, `data-oc-align`, `data-oc-animation` | `fill`       |

`Skeleton`은 기본 장식이라 `aria-hidden="true"`를 강제하고 consumer의 `role`/`tabIndex` prop을 무시합니다. 폭·높이·radius·색·opacity는 `--oc-skeleton-*` 변수로 노출됩니다.

## Action Area 계약

`ActionAreaLayout`은 스크롤 가능한 body와 고정 footer slot 사이의 scroll 경계를 소유합니다.

| Component                 | Root selector                                   | Visual selectors                               | Stable parts                                                      |
| ------------------------- | ----------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------- |
| `ActionArea`              | `data-oc-component="action-area"`               | `data-oc-variant`, `data-oc-background="true"` | `actions`, `caption`, `extra-slot`, `compact-row`, `compact-slot` |
| `ActionAreaLayout`        | `data-oc-component="action-area-layout"`        | `data-oc-scrollable="true\|false"`             | -                                                                 |
| `ActionAreaLayout.Body`   | `data-oc-component="action-area-layout-body"`   | -                                              | `body`                                                            |
| `ActionAreaLayout.Footer` | `data-oc-component="action-area-layout-footer"` | -                                              | `footer`                                                          |

- `data-oc-scrollable="true"`는 body에 보이는 scrollport 밖 overflow가 있다는 뜻입니다. 이 상태에서 직속 footer `ActionArea`는 block-end padding을 유지하고 `--oc-action-area-padding-block-start: 0`을 받으며, 대응하는 block-start 간격은 body에 예약됩니다.
- `ActionAreaLayout.ActionArea background="auto"`는 scroll 상태를 자식 `ActionArea`의 background 계약에 매핑합니다: body가 스크롤 가능하고 바닥에 닿지 않은 동안만 `data-oc-background="true"`가 방출됩니다. `data-oc-background`는 시각 gradient/background 상태이며 body 예약의 근거로 사용하지 마세요.

## FloatingWindow 계약

FloatingWindow는 anchor 기반 geometry와 dialog 시맨틱을 Modal 기본 surface presentation에 결합합니다. 기본 `FloatingWindowRoot`는 modeless이고 `ModalWindowRoot`는 modal focus 정책, 배경 입력을 차단하는 backdrop, Modal popup scale/fade motion을 적용합니다. ModalWindow backdrop은 기본적으로 렌더링하며 필요한 경우 `backdrop="hidden"`으로 시각적 dimmer만 숨길 수 있습니다.

| Layer/slot  | Selector                                                                   | 책임                                         |
| ----------- | -------------------------------------------------------------------------- | -------------------------------------------- |
| Backdrop    | `data-oc-component="floating-window-backdrop"` + `data-oc-part="backdrop"` | ModalWindow 기본 dimmer와 배경 pointer 차단  |
| Positioner  | `data-oc-component="floating-window-positioner"`                           | fixed positioning과 manager 주입 stack level |
| Window      | `data-oc-component="floating-window"` + `data-oc-part="window"`            | dialog 시맨틱과 anchor/free 좌표             |
| Drag handle | `data-oc-part="drag-handle"` + `data-oc-draggable="true\|false"`           | 명시적인 pointer drag 시작 영역              |
| Surface     | `data-oc-part="surface-motion"` + `data-oc-part="surface"`                 | Modal popup presentation과 `data-oc-resize`  |
| Header      | `data-oc-component="modal-header"`                                         | Modal 기본 header slot                       |
| Navigation  | `data-oc-component="modal-navigation-section"`                             | Modal 기본 disclosure navigation             |
| Body        | `data-oc-component="modal-body"`                                           | Modal 기본 scrollable body                   |
| Footer      | `data-oc-component="modal-footer"`                                         | Modal 기본 footer slot                       |
| Close       | `data-oc-component="modal-close"`                                          | dialog close action                          |

Window은 `data-oc-positioned="true\|false"`, `data-oc-window-positioning="anchored\|free"`, `data-oc-window-presentation="floating\|modal"`, `data-oc-window-modality="modeless\|trap-focus\|modal"`, `data-oc-side`, `data-oc-alignment`를 노출합니다. presentation은 사용한 Root 구성을 구분하는 호환 메타데이터이며 모든 presentation은 동일한 Modal popup surface motion을 사용합니다. modality는 focus와 background interaction 계약을 나타냅니다. pointer drag 중에는 `data-oc-window-dragging="true"`가 설정됩니다. `data-oc-side`는 collision flip 뒤 실제 side이고, `data-oc-alignment`는 alignment flip 뒤의 semantic alignment입니다. alignment shift는 같은 alignment 의도를 유지한 채 좌표만 boundary 안으로 보정합니다.

Drag handle 내부의 `button`, `a`, form control, `contenteditable` 요소는 drag를 시작하지 않습니다. 그 밖의 custom interactive target은 `data-oc-window-no-drag`로 같은 동작을 선언할 수 있습니다. 이 attribute는 drag gesture의 opt-out 계약이며 시각 selector가 아닙니다.

Manager는 일반 pointer와 focus intent를 window activation으로 해석하지만 `data-oc-part="close"` intent는 activation에서 제외합니다. 따라서 클릭 가능한 비활성 modeless window의 Close는 stack 순서를 바꾸지 않고 해당 위치에서 종료 모션을 실행합니다. modal backdrop과 inert 영역 뒤의 window는 이 예외와 관계없이 상호작용할 수 없습니다.

`FloatingWindowHeader`와 `FloatingWindowNavigationSection`은 surface 최상단을 소유하는 대체 관계의 chrome입니다. 기본 조합에서는 둘을 함께 렌더링하지 않습니다. `FloatingWindowNavigationSection`은 기본적으로 title을 dialog title로 등록하며, 별도의 custom title owner를 제공할 때만 `titleAsDialogTitle={false}`를 사용합니다.

modeless `FloatingWindowRoot`는 열릴 때 현재 focus를 유지합니다. `ModalWindowRoot`는 Modal과 같은 초기 focus resolver를 사용해 등록된 visible title을 우선하고, title이 없으면 첫 tabbable 요소로 fallback합니다. 이 정책은 DOM상 close button이 먼저 있다는 이유만으로 닫기 동작을 자동 강조하지 않기 위한 dialog interaction 계약입니다.

FloatingWindow surface 크기·radius·padding은 Modal의 `--oc-modal-*` popup 계약을 사용합니다. `resize="fixed"`는 Modal fixed surface와 같은 내부 body scroll contract를 활성화하며, surface의 `data-oc-scrollable`, Header의 `data-oc-sticky`, Footer의 `data-oc-background`를 실제 scroll state에 맞춰 갱신합니다. Footer의 ActionArea 조합은 Modal과 같은 direct-child selector 계약을 사용합니다.

`FloatingWindowManager` 안의 window는 고유한 `windowId`로 등록되며 다음 managed state를 노출합니다.

| Selector                            | 의미                                         |
| ----------------------------------- | -------------------------------------------- |
| `[data-oc-window-active='true']`    | 현재 manager stack의 활성 window             |
| `[data-oc-window-active='false']`   | 열려 있지만 비활성인 managed window          |
| `--oc-floating-window-stack-offset` | manager가 정규화한 backdrop/window 쌍 offset |

Manager는 modeless window보다 modal과 `trap-focus` window를 위에 유지합니다. 동일 modality tier에서는 open, pointer, focus 활성화 순서가 stack을 결정하며 Escape와 outside dismissal은 활성 window 하나에만 허용됩니다. Manager 밖에서는 `data-oc-window-active`를 출력하지 않습니다.

여러 focus-owning window가 열려도 Base UI modal/inert ownership은 활성 window 하나에만 부여됩니다. 비활성 modal과 `trap-focus` window는 요청 modality와 stack tier를 보존하지만 아래에 있는 동안 document inert를 중복 적용하지 않습니다.

| Variable                            | Default                                              | Scope                         |
| ----------------------------------- | ---------------------------------------------------- | ----------------------------- |
| `--oc-floating-window-stack-offset` | `0`; `stackLevel` prop이 manager-owned offset을 주입 | backdrop/window stacking pair |

`--oc-floating-window-x`, `--oc-floating-window-y`는 geometry runtime이 소유하는 live 좌표이므로 consumer style override 대상으로 사용하지 않습니다.

## Modal 레이어 계약

Modal은 transition motion과 시각 clipping을 분리합니다.

| Layer          | Selector                                                                                                    | 책임                                                                                                       |
| -------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Backdrop       | `data-oc-component="modal-backdrop"` + `data-oc-part="backdrop"`                                            | dimmer 표시와 backdrop press dismissal                                                                     |
| Motion layer   | `data-oc-component="modal-content"` + `data-oc-part="surface-motion"`                                       | dialog 시맨틱, Base UI transition 상태, opacity/transform/drag translate                                   |
| Visual surface | `data-oc-part="surface"`                                                                                    | background, radius, overflow clipping, layout, header/body/footer slot, `data-oc-scrollable="true\|false"` |
| Bottom fill    | `data-oc-component="modal-bottom-fill"` + `data-oc-part="bottom-fill"`                                      | bottom sheet overshoot fill                                                                                |
| Header         | `data-oc-component="modal-header"` + `data-oc-part="header"`                                                | 타이틀 영역과 optional drag handle                                                                         |
| Navigation     | `data-oc-component="modal-navigation-section"`                                                              | 고정 chrome row + 확장 panel                                                                               |
| Body           | `data-oc-component="modal-body"` + `data-oc-part="body"`                                                    | scroll area wrapper                                                                                        |
| Body content   | `data-oc-component="modal-body-content"` + `data-oc-part="body-content"` + `data-oc-layout="content\|fill"` | padded content 영역                                                                                        |
| Footer         | `data-oc-component="modal-footer"` + `data-oc-part="footer"`                                                | 고정 overlay action/footer slot                                                                            |

시각 커스터마이즈는 `data-oc-part="surface"` 또는 component contract 변수를 대상으로 하세요. `surface-motion`은 transition/placement/drag 동작을 의도적으로 바꿀 때만 대상입니다.

Modal의 기본 `initialFocus="auto"`는 `ModalHeader`의 visible title을 `tabIndex={-1}`인 정적 문맥 시작점으로 사용합니다. title이 없으면 첫 tabbable 요소로 fallback합니다. `first-interactive`, `title`, ref/function으로 업무 의미에 맞는 대상을 명시할 수 있으며, 되돌리기 어려운 확인 dialog는 가장 덜 파괴적인 action을 지정하는 것이 권장됩니다. title focus는 일반 Tab 순서에 추가되지 않으며 첫 Tab부터 기존 control 순환이 시작됩니다.

- `ModalContent`의 `className`은 motion layer, visual surface, bottom fill 세 레이어에 적용됩니다 — 공개 `--oc-modal-*` 변수 override가 layout 계산까지 도달하게 하기 위함입니다. `style`은 visual surface에 적용되며, `style`의 CSS custom property는 motion/bottom fill 레이어로도 전달됩니다.
- `data-starting-style`/`data-ending-style`은 Base UI가 motion layer에 방출합니다.
- `data-oc-modal-backdrop="hidden"`으로 정착하면 backdrop 레이어는 렌더링되지 않습니다. 전환 중 잠시 남는 exiting backdrop 요소에 layout/hit-testing/커스터마이즈를 의존하지 마세요.
- `backdropFrom` snap 임계 아래에 정착한 bottom sheet는 backdrop 레이어에 `data-oc-modal-backdrop-passthrough="true"`를 방출합니다. 이 상태는 non-modal이며 배경이 상호작용 가능합니다. attribute는 드래그 프레임 단위가 아니라 정착(rest) 시점에 바뀝니다.
- modal elevation shadow와 backdrop dim은 하나의 깊이 신호의 보완 관계입니다(매 프레임 `shadow = 1 − dim`). dim 상태 정착은 shadow 없음, undimmed/hidden-backdrop 정착은 full shadow이며, `backdropFrom` 경계를 지나는 이동은 둘을 crossfade합니다.
- visual surface의 `data-oc-scrollable`은 footer 예약 스타일링용입니다. `ActionArea` footer가 있을 때 `true`면 footer action area의 block-start padding이 body content에 예약되고, `false`면 standalone action area padding 모델이 유지되고 footer 높이만 예약됩니다.
- bottom placement에서 motion layer가 sheet geometry를 소유합니다: `--oc-modal-sheet-height`가 높이를 제어할 때 `data-oc-modal-sheet-sizing="managed"`, 높이 변화가 sheet motion 계약으로 전환되어야 할 때만 `data-oc-modal-sheet-transition="auto"`, snap-point 동작 중 `data-oc-modal-snapping="true"`.
- `resize="fixed"`는 `--oc-modal-fixed-block-size`를 요청 높이로 사용하고 `--oc-modal-fixed-max-block-size`(기본 `--oc-modal-max-height`)로 clamp합니다. popup placement는 `--oc-modal-popup-fixed-max-block-size`로 별도 clamp됩니다.
- `ModalBody layout="fill"`은 header/footer/content padding 예약을 유지한 채 body content slot을 iframe·map·viewer 같은 embedded 표면용 fill container로 바꿉니다.
- size별 공개 변수: `--oc-modal-small-width`/`--oc-modal-medium-width`/`--oc-modal-large-width`와 `--oc-modal-{size}-padding-*`이 내부 `--oc-modal-container-padding-*` 계약으로 매핑됩니다. header/body/footer padding은 기본적으로 container padding을 상속하며, `--oc-modal-header-padding-*`/`--oc-modal-footer-padding-*`로 slot별 예외를 둘 수 있습니다. edge 정렬 chrome control용으로 `--oc-modal-header-edge-offset-inline`이 있습니다.
- 닫기 시도가 차단되어 피드백 중일 때 motion layer에 `data-oc-modal-feedback="reject"`가 방출됩니다. transform을 직접 덮지 말고 `--oc-modal-reject-distance` 같은 contract 변수를 사용하세요.
- `ModalNavigationSection`은 body content가 아니라 Modal chrome입니다. 루트는 `data-oc-state="open|closed"`, toggle row는 `data-oc-component="modal-navigation"` + `data-oc-part="navigation"` + `aria-expanded`/`aria-controls`, indicator는 `data-oc-part="indicator"`/`indicator-frame`, panel은 `data-oc-component="modal-navigation-panel"`(collapsed 동안 `aria-hidden`/`inert`), panel content는 `data-oc-part="panel-inner"`를 노출합니다.

## Provider 소유 예외

다음 non-`data-oc-*` attribute는 컴포넌트 소유 계약이 아니라 외부 provider/foundation 소유입니다.

| Attribute             | Owner                          | 정책                                   |
| --------------------- | ------------------------------ | -------------------------------------- |
| `data-side`           | Base UI / positioning provider | provider 상태 스타일링에 사용 가능     |
| `data-starting-style` | Base UI / transition provider  | transition 상태 스타일링에 사용 가능   |
| `data-ending-style`   | Base UI / transition provider  | transition 상태 스타일링에 사용 가능   |
| `data-theme`          | Foundation theme 계약          | 테마 선택용. 컴포넌트 selector 계약 밖 |

## Legacy 호환 출력

아래 attribute는 구형 consumer 호환용으로만 방출됩니다. 새 스타일링에 사용하지 마세요. 제거는 major 릴리스에서만 진행됩니다([Stability Policy](./stability.md) 참조).

| Component                            | Legacy attribute        | Replacement                |
| ------------------------------------ | ----------------------- | -------------------------- |
| `SectionHeader`                      | `data-size`             | `data-oc-size`             |
| `SectionHeader`                      | `data-viewport`         | `data-oc-viewport`         |
| `TextInput`                          | `data-invalid`          | `data-oc-invalid`          |
| `TextInput`                          | `data-interactive`      | `data-oc-interactive`      |
| `Select`                             | `data-invalid`          | `data-oc-invalid`          |
| `Checkbox`                           | `data-indeterminate`    | `data-oc-indeterminate`    |
| `ScrollArea`                         | `data-root`             | `data-oc-scroll-root`      |
| `ScrollArea`                         | `data-scroll`           | `data-oc-scroll`           |
| `ScrollArea`                         | `data-scroll-label`     | `data-oc-scroll-label`     |
| `ScrollAreaContainer`                | `data-scroll-container` | `data-oc-scroll-container` |
| `ScrollArea` scrollbar               | `data-orientation`      | `data-oc-orientation`      |
| `ScrollArea` scrollbar, track, thumb | `data-state`            | `data-oc-state`            |
