Featuring Design System
ChangelogComponents

0.1.18

TextArea Root/Input compound 분해와 Select.ItemIndicator 제거 두 갈래 breaking과 함께, focusableWhenDisabled 모드 / Field.required ctx 전파 / Item ctx propagation / leaf own override 일괄을 도입합니다.

이번 릴리즈는 두 갈래의 breaking — TextArea compound 분해와 Select.ItemIndicator 제거 — 를 포함합니다. 동시에 disabled 상태의 컴포넌트가 Tooltip.Trigger로 감싸일 때 hover/focus가 정상 동작하도록 focusableWhenDisabled 모드를 다수 컴포넌트에 도입하고, leaf component의 own override 패턴(disabled? / readOnly? / size? / loading?)을 일괄 정리합니다.

Breaking Changes — TextArea Root/Input compound 분해

TextArea가 callable 단일 컴포넌트에서 TextArea.Root + TextArea.Input compound로 재구성됩니다. 다른 form primitive(TextInput, Field, Select 등)와 같은 레이아웃·상태 전파 패턴을 따릅니다.

/* Before */
<TextArea status="error" disabled rows={4} placeholder="..." />

/* After */
<TextArea.Root status="error" disabled>
  <TextArea.Input rows={4} placeholder="..." />
</TextArea.Root>
BeforeAfter
TextArea (callable)TextArea.Root + TextArea.Input (compound)
TextAreaPropsTextAreaRootProps + TextAreaInputProps
(단일 <textarea>)Root는 <div> (border/radius/bg/padding), Input은 <textarea> (typography/scroll)

Breaking<TextArea ... /> 사용처는 모두 <TextArea.Root> + <TextArea.Input> 패턴으로 교체해 주세요. TextAreaProps를 import한 코드는 TextAreaRootProps 또는 TextAreaInputProps로 분리됩니다. Root에 size / status / disabled / readOnly를, Input에 rows / placeholder 등을 두는 것이 권장 분리입니다. 시각은 동일합니다.

TextAreaContextsize / disabled / readOnly를 자식 Input에 전파하므로, 같은 값을 두 번 적을 필요가 없습니다.

Skeleton.TextArea에도 size? prop이 추가되어 line-height × rows 기반으로 자리 크기가 계산됩니다. 로딩이 끝나고 실제 TextArea로 치환될 때 layout shifting이 발생하지 않습니다.

Breaking Changes — Select.ItemIndicator 제거

Select.ItemIndicator 컴포넌트가 제거됩니다. 동일 역할은 다중 선택일 때 Select.ItemCheckbox, 단일 선택일 때 Select.ItemRadio가 담당합니다.

BreakingSelect.ItemIndicator / SelectItemIndicator / SelectItemIndicatorProps 사용처는 Select.ItemCheckbox 또는 Select.ItemRadio로 교체해 주세요. itemIndicatorClass 스타일도 함께 제거됩니다.

focusableWhenDisabled 모드

disabled 상태에서도 포커스를 유지해 hover/focus 이벤트가 발화되도록 하는 focusableWhenDisabled? prop이 다수 컴포넌트에 추가됩니다. Tooltip.Trigger로 감싸 disabled 컨트롤에 도움말을 다는 패턴이 정상 동작합니다.

대상: Tag.Root, Link.Root, Dropdown.Trigger, Select.Trigger. (이미 지원하던 Pagination triggers는 동작 일관성 정리.)

<Tooltip.Root>
  <Tooltip.Trigger
    render={
      <Button.Root disabled focusableWhenDisabled>
        <Button.Text>저장</Button.Text>
      </Button.Root>
    }
  />
  <Tooltip.Popup>변경 사항이 없습니다</Tooltip.Popup>
</Tooltip.Root>

native disabled는 keyboard / touch / screen reader 사용자가 도달하지 못하므로, 완전한 a11y를 위해 focusableWhenDisabled를 명시하는 패턴을 권장합니다. Tooltip.Trigger는 dev 모드에서 disabled child가 focusableWhenDisabled 없이 들어오면 console warn을 출력합니다.

이 모드의 동작에는 @featuring-corp/design-tokens@0.1.1의 normalize 변경이 함께 반영됩니다. components/preset/* 또는 동등 버전의 design-tokens normalize를 사용하는 환경에서 정상 동작합니다.

Field.required 컨텍스트 전파

Field.Rootrequired? prop이 추가되고, FieldContext로 자식에 전파됩니다.

<Field.Root required>
  <Field.Label>이메일</Field.Label>
  <TextInput.Root>
    <TextInput.Input />
  </TextInput.Root>
</Field.Root>
  • Field.Label* 인디케이터를 자동 표시 (own required prop으로 override 가능)
  • TextInput.Input / TextArea.Input / Select.Rootaria-required="true"를 자동 부여

Field.Messagestatus="error"일 때 role="alert", 그 외에는 aria-live="polite"로 분기되어 검증 메시지 가시성이 개선됩니다.

Item ctx propagation (Dropdown / Menu / Select)

Dropdown.Item / Dropdown.Action이 자식 ItemCheckbox / ItemRadio에 disabled / size를 컨텍스트로 자동 전파합니다. Menu / Select도 동일하게 동작합니다.

{/* Item에 disabled를 한 번만 박으면 자식 indicator도 자동으로 disabled */}
<Menu.Item disabled>
  <Menu.ItemCheckbox checked={value} />
  내보내기
</Menu.Item>

base-ui parent(BaseMenu.Item / BaseSelect.Item)가 cloneElement로 박는 data-disabled도 own > inherited 우선순위로 흡수합니다. own이 명시되면 inherited를 무시하므로 disabled={false}로 부모의 disabled를 override 하는 것도 허용합니다.

Menu.ItemCheckbox / Menu.ItemRadio / Select.ItemCheckbox / Select.ItemRadio에서 disabled prop은 더 이상 직접 받지 않습니다 — 부모 Item에 박아 주세요. 시각 contract와 props 타입은 Dropdown* 단일 소스를 그대로 extends 합니다.

Leaf own override 일괄 적용

compound leaf가 own > ctx > fallback 패턴으로 prop을 받을 수 있도록 다음 컴포넌트에 own prop이 확장됩니다.

컴포넌트추가 own prop
Button.Icondisabled?, loading?
Tag.Icon, Tag.RemoveButtondisabled?
Tabs.Icondisabled?, active?
SegmentedControl.Icondisabled?, selected?
TextInput.Input, TextInput.Icondisabled?, readOnly?
TextArea.Inputdisabled?, readOnly?, size?
Label.Textdisabled?
Select.Action, Select.Buttonsize?

함께 size prop의 own > ctx > fallback 우선순위가 모든 leaf에서 일관되게 적용됩니다 — 이전 0.1.17까지는 일부 leaf에서만 동작했습니다.

prop 결합 연산자도 ||?? 로 통일됩니다. disabled={false} / readOnly={false}로 컨텍스트의 값을 끄는 것이 허용됩니다.

Tabs / SegmentedControl 시각 정합

Tabs는 Base UI Tabs.Indicator를 자동 렌더해 List/Panels에서 활성 탭의 underline이 따라 움직입니다. 동시에 hover / press preview transition이 추가되어 비활성 탭의 호버·프레스 상태에서 미세한 underline preview가 표시됩니다.

SegmentedControl은 내부적으로 Base UI Toggle.Group / Toggle로 위임되도록 리팩터되어, single-select / multi-select 동작이 표준 Toggle 의미론에 정렬됩니다. 시각·동작은 동일합니다.

내부 정리

  • useRenderComponentstateAttributesMapping 옵션이 도입되고, state.disabled / state.readOnly가 truthy일 때 data-* + aria-*가 자동으로 부여됩니다. 각 컴포넌트가 수동으로 박던 extraProps: { 'data-disabled': ..., 'aria-readonly': ... }가 일괄 제거되었습니다 (소비자 영향 없음, 내부 일관성).
  • Menu / SelectItem / Action / Button / ItemCheckbox / ItemRadio props 타입이 Dropdown* 타입을 직접 extends 하도록 통합됩니다. 시각 contract가 단일 소스로 모입니다.
  • Select.Root의 base-ui 패스스루 14개 prop이 ...baseProps spread로 단순화됩니다.
  • select-prim / multi-select-prim (CoreSelectPrim / CoreMultiSelectPrim) 내부 디렉터리가 다른 Core*와 동일한 legacy/ 경로로 이동합니다. 소비자가 named import로 사용하므로 외부 영향은 없습니다.
  • CSS 가드 셀렉터가 :not(:disabled, [data-disabled]) 형태로 통일되어 native disabled와 base-ui data-disabled를 동시에 가드합니다.
  • Calendar.Closechildren=function 분기에서 hook 순서가 깨지던 문제를 별도 컴포넌트로 분리해 해소합니다.
  • Tag / Link CSS의 pointer-events: none을 제거(cursor: not-allowed 유지)해 disabled+Tooltip 결합이 가능해집니다. click 차단은 핸들러에서 처리됩니다.
  • Tooltip 스타일이 레거시 typoVariant / flex 의존을 끊고 typoPropertyMap['$body-1']을 인라인으로 사용합니다.