Filter | CoUI
본문으로 건너뛰기
LogoCoUI

Filter

상호 배타적인 옵션을 토글 버튼 그룹으로 묶는 분절형 필터 컨트롤

목차

여러 옵션을 라디오 그룹처럼 상호 배타적으로 선택하는 분절형 필터 컨트롤입니다. Flutter/Web 양쪽에서 동일한 API(Filter)를 제공합니다.

Filter는 controlled 컴포넌트입니다 — 선택값은 부모가 groupValue로 소유하고, onValueChanged 콜백으로 상태를 갱신합니다. 선택적으로 reset 버튼을 통해 선택을 해제할 수 있습니다.

세그먼트는 ToggleGroup을 합성해 그립니다 — 트랙, 올라온 선택 면과 그림자, 칸 크기, 라벨 글자, 비활성 모양은 ToggleGroup 의 것이고 Filter 는 그 위에 reset 버튼과 구분선만 더합니다.

기본 사용법 (Basic Usage)#

Filter<String>(
  groupValue: _selected,
  onValueChanged: (value) => setState(() => _selected = value),
  onReset: () => setState(() => _selected = null),
  showResetButton: true,
  items: const [
    CoreFilterItem(value: 'svelte', label: 'Svelte'),
    CoreFilterItem(value: 'vue', label: 'Vue'),
    CoreFilterItem(value: 'react', label: 'React'),
  ],
)
Filter<String>(
  groupValue: _selected,
  onValueChanged: (value) => setState(() => _selected = value),
  onReset: () => setState(() => _selected = null),
  showResetButton: true,
  items: const [
    CoreFilterItem(value: 'svelte', label: 'Svelte'),
    CoreFilterItem(value: 'vue', label: 'Vue'),
    CoreFilterItem(value: 'react', label: 'React'),
  ],
)

빠른 오버라이드 (Chain)#

이미 만든 Filter 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius16처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius16 == CoreRadius.radius16) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.

class FilterChainExample extends StatefulWidget {
  const FilterChainExample({super.key});

  @override
  State<FilterChainExample> createState() => _FilterChainExampleState();
}

class _FilterChainExampleState extends State<FilterChainExample> {
  String? _selected = 'svelte';

  void handleValueChanged(String value) {
    setState(() => _selected = value);
  }

  void handleReset() {
    setState(() => _selected = null);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        Filter<String>(
          groupValue: _selected,
          onValueChanged: handleValueChanged,
          onReset: handleReset,
          showResetButton: true,
          items: const [
            CoreFilterItem(value: 'svelte', label: 'Svelte'),
            CoreFilterItem(value: 'vue', label: 'Vue'),
            CoreFilterItem(value: 'react', label: 'React'),
          ],
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        Filter<String>(
          groupValue: _selected,
          onValueChanged: handleValueChanged,
          onReset: handleReset,
          showResetButton: true,
          items: const [
            CoreFilterItem(value: 'svelte', label: 'Svelte'),
            CoreFilterItem(value: 'vue', label: 'Vue'),
            CoreFilterItem(value: 'react', label: 'React'),
          ],
        ).withStyle(
          const CoreFilterStyle(
            toggleGroupStyle: CoreToggleGroupStyle(
              backgroundColor: CoreColor.token(CoreColors.tertiary),
              borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
              itemBorderRadius: CoreBorderRadius.all(CoreRadius.radius8),
              padding: CoreEdgeInsets.symmetric(
                horizontal: CoreSpace.space24,
                vertical: CoreSpace.space8,
              ),
            ),
          ),
        ),
      ],
    );
  }
}
class FilterChainExample extends StatefulComponent {
  const FilterChainExample({super.key});

  @override
  State<FilterChainExample> createState() => _FilterChainExampleState();
}

class _FilterChainExampleState extends State<FilterChainExample> {
  String? _selected = 'svelte';

  void handleValueChanged(String value) {
    setState(() => _selected = value);
  }

  void handleReset() {
    setState(() => _selected = null);
  }

  @override
  Component build(BuildContext context) {
    return div(
      [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        Filter<String>(
          groupValue: _selected,
          onValueChanged: handleValueChanged,
          onReset: handleReset,
          showResetButton: true,
          items: const [
            CoreFilterItem(value: 'svelte', label: 'Svelte'),
            CoreFilterItem(value: 'vue', label: 'Vue'),
            CoreFilterItem(value: 'react', label: 'React'),
          ],
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        Filter<String>(
          groupValue: _selected,
          onValueChanged: handleValueChanged,
          onReset: handleReset,
          showResetButton: true,
          items: const [
            CoreFilterItem(value: 'svelte', label: 'Svelte'),
            CoreFilterItem(value: 'vue', label: 'Vue'),
            CoreFilterItem(value: 'react', label: 'React'),
          ],
        ).withStyle(
          const CoreFilterStyle(
            toggleGroupStyle: CoreToggleGroupStyle(
              backgroundColor: CoreColor.token(CoreColors.tertiary),
              borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
              itemBorderRadius: CoreBorderRadius.all(CoreRadius.radius8),
              padding: CoreEdgeInsets.symmetric(
                horizontal: CoreSpace.space24,
                vertical: CoreSpace.space8,
              ),
            ),
          ),
        ),
      ],
      classes: 'flex flex-col items-start',
    );
  }
}

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 한정된 옵션 중 하나를 빠르게 전환해야 할 때 (프레임워크, 정렬 기준, 카테고리 등)
  • 옵션이 항상 화면에 모두 노출되어야 할 때
  • 선택 해제(reset) 기능이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • RadioGroup: 라디오 입력의 폼·키보드 계약이 필요할 때
  • Tabs: 값 선택 요청뿐 아니라 콘텐츠 패널 전환도 필요할 때
  • ChipGroup: 값 하나 대신 선택 집합을 관리해야 할 때

Props / Parameters#

T 는 선택 값, W 는 Flutter Widget / Web Jaspr Component 입니다.

속성타입기본값설명
variant CoreFilterVariant segmented 시각 변형
size CoreComponentSize md 크기 단계. 합성한 ToggleGroup 에 그대로 넘깁니다 — md 는 트랙 40(칸 32 · reset 버튼 32×32 · 구분선 16), sm 은 트랙 32(칸 26 · reset 버튼 32×26 · 구분선 14). sm 이 아닌 다른 값은 md 로 읽습니다. Filter 만의 크기 enum 은 없습니다.
groupValueT?필수현재 선택 값
items List<CoreFilterItem<T, W>> 필수 필터 옵션 목록
onValueChanged void Function(T) 필수 항목 선택 요청
onReset void Function()? null reset 요청
showResetButton bool false reset 버튼 표시
enabled bool? null null 이면 활성. false 이면 입력을 받지 않고 비활성 모양(트랙 disabled-container , 글자 disabled-content , 선택 칸 그림자 없음)을 그립니다.
filterStyle CoreFilterStyle? null 스타일 단일 슬롯

CoreFilterItem#

CoreFilterItem<T, W>는 세그먼트 하나를 기술합니다. W는 플랫폼 위젯 타입(Flutter Widget / Web Component)이며 items: const [CoreFilterItem(value: …, label: …)]처럼 인라인으로 쓰면 타입 추론이 채워 줍니다.

속성타입기본값설명
value T — 이 세그먼트가 나타내는 값. groupValue와 ==로 비교해 선택 여부를 정합니다.
label String — 세그먼트 라벨. showLabel: false 여도 접근성 이름( aria-label / Semantics(label:) )으로 남습니다.
icon W? null 라벨 앞에 그리는 호출자 제공 아이콘. 선택 상태에 따라 iconStyle / selectedIconStyle 의 색·크기가 ambient IconTheme 으로 전달됩니다.
showLabel bool true 라벨 박스를 그릴지 여부. icon != null && showLabel == false 이면 아이콘 전용 세그먼트(칸의 최소 폭이 칸 높이라 md 32×32 · sm 26×26 정사각)가 됩니다.
Filter<String>(
  groupValue: _selected,
  onValueChanged: (value) => setState(() => _selected = value),
  items: [
    const CoreFilterItem(value: 'all', label: 'All'),
    CoreFilterItem(
      value: 'starred',
      label: 'Starred',
      icon: const Icon(LucideIcons.star),
      showLabel: false, // icon-only 32×32 segment
    ),
  ],
)

스타일 시스템 (Style System)#

모든 시각 chrome은 단일 filterStyle: CoreFilterStyle? 슬롯으로 들어갑니다. 트랙·칸·선택 면은 합성한 ToggleGroup 의 스타일이라 toggleGroupStyle 슬롯으로 덮고, Filter 가 직접 정하는 것은 reset 버튼(resetButtonStyle)과 구분선(dividerStyle · dividerHeight)과 그 앞뒤 간격입니다. 마지막 칸에서 구분선까지는 dividerLeadingSpacing(기본 6), 구분선에서 ✕ 버튼까지는 trailingSpacing(기본 2)이며, ✕ 버튼에 자체 여백이 있어 2 만으로도 양쪽이 비슷하게 보입니다. 비활성일 때는 칸과 함께 ✕ 도 disabledContent 색입니다. 예전의 평면 필드(backgroundColor · itemPadding 등)는 deprecated 이며 아직 toggleGroupStyle 아래로 접혀 들어갑니다.

Filter<String>(
  groupValue: _selected,
  onValueChanged: handleValueChanged,
  items: items,
  filterStyle: const CoreFilterStyle(
    toggleGroupStyle: CoreToggleGroupStyle(
      backgroundColor: CoreColor.token(CoreColors.surfaceContainerHigh),
      selectedColor: CoreColor.token(CoreColors.surfaceRaised),
      itemSpacing: CoreSpace.space2,
    ),
  ),
)

CoreFilterStyle 필드#

필드타입설명
backgroundColor CoreColor? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Background fill of the filter container.
selectedColor CoreColor? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Background fill of the currently selected filter item.
borderRadius CoreBorderRadius? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Container corner radius.
itemBorderRadius CoreBorderRadius? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Per-item corner radius.
padding CoreEdgeInsets? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Padding inside the filter container.
itemPadding CoreEdgeInsets? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Padding inside each filter item — the segment's own uniform inset around [icon][label box] . Read by both platform resolvers and the Figma builder.
labelPadding CoreEdgeInsets? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Inset of the label box inside a segment (painted only when the item shows its label). Read by both platform resolvers and the Figma builder.
itemHeight double? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Fixed segment height (logical px); content centres vertically. Read by both platform resolvers and the Figma builder.
itemSpacing double? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Spacing between filter items (logical px). Maps to native Row.spacing on Flutter and CSS flex gap on Web.
resetButtonStyle CoreButtonStyle? Reset (clear-selection) button style override. The single entry point for the reset-affordance chrome — square side ( width / height ), padding, corner radius, hover transition ( animationDuration ), rest / hover foreground, and the close glyph ( leadingIconStyle ). Recursively merged onto [defaultResetButtonStyle] and raw-forwarded to Button(variant:.ghost, size:.sm, shape:.square, buttonStyle: …) (the ghost variant owns the transparent-fill / hover semantics).
selectedFontWeight int? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Font weight applied to the currently selected item label (CSS font-weight integer).
transitionDuration Duration? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Selection-state colour transition duration.
labelTextStyle CoreTextStyle? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Label text style override applied to every unselected filter item label and the reset icon. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw foregroundColor field removed).
selectedLabelTextStyle CoreTextStyle? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Label text style override applied to the currently selected item label. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw selectedForegroundColor field removed).
iconStyle CoreIconStyle? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Nested [CoreIconStyle] slot applied to an unselected segment's caller-supplied icon (size + colour). Merged onto [defaultIconStyle] and raw-forwarded as the icon slot's ambient icon theme on both platforms.
selectedIconStyle CoreIconStyle? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Nested [CoreIconStyle] slot applied to the selected segment's caller-supplied icon. Merged onto [defaultSelectedIconStyle] and raw-forwarded on both platforms.
clickableStyle CoreClickableStyle? Deprecated (since 0.173) — Use toggleGroupStyle — the segments are a composed ToggleGroup now; the value still reaches it until removed. Nested [CoreClickableStyle] slot for the composed per-item Clickable (press scale / durations / focus ring / disabled opacity). Merged on top of [defaultClickableStyle] and raw-forwarded — the Clickable's own resolver fills the rest.
toggleGroupStyle CoreToggleGroupStyle? Nested [CoreToggleGroupStyle] slot for the composed segments. Merged on top of [defaultToggleGroupStyle] (and the deprecated flat fields, see [toggleGroupStyleFor]) and raw-forwarded to ToggleGroup(toggleGroupStyle:) — the group's own resolver fills the rest.
dividerStyle CoreDividerStyle? Nested [CoreDividerStyle] slot of the hairline between the segments and the reset button. Merged on top of [defaultDividerStyle] and raw-forwarded to Divider(dividerStyle:) .
disabledDividerColor CoreColor? Colour of the hairline while the filter is disabled. Applied on top of [dividerStyle] only when the filter is disabled; falls back to [defaultDisabledDividerColor].
dividerHeight double? Length of the hairline (logical px).
dividerLeadingSpacing double? Distance from the last segment to the hairline (logical px, edge to edge). Counted from the segment, not from the group's own item gap.
trailingSpacing double? Spacing between the hairline and the reset button (logical px).

테마 커스터마이징 (Theme)#

CoreFilterTheme으로 프로젝트 레벨 스타일 오버라이드가 가능합니다.

CoreComponentTheme(
  filter: CoreFilterTheme(
    style: CoreFilterStyle(
      toggleGroupStyle: CoreToggleGroupStyle(
        backgroundColor: CoreColor.token(CoreColors.surfaceContainerHigh),
      ),
    ),
  ),
)

Resolve 우선순위: 디자인 시스템 기본값 → CoreFilterTheme.style → CoreFilterTheme.variantStyles[variant] → 위젯 filterStyle.

변형 (Variants)#

비활성#

enabled: false 로 끕니다. 트랙은 disabled-container, 글자는 disabled-content 이고 선택 칸은 surface-container-high 로 가라앉아 그림자를 그리지 않습니다.

동작 스펙 (Behavior)#

  • groupValue == item.value 로 선택을 표시합니다. 항목 활성화는 onValueChanged(item.value) 를 호출하고 부모가 값을 갱신해야 선택이 바뀝니다. reset도 onReset 요청만 하며 자체 값을 지우지 않습니다.
  • 필터링할 데이터·본문 패널을 소유하지 않습니다. 호출부가 목록 필터 결과를 갱신합니다.
  • enabled: false 이면 입력을 받지 않고 reset 버튼도 누를 수 없습니다. 칸 하나만 비활성화하는 플래그는 없습니다.

시각#

  • 트랙 surface-container-high, 모서리 10(md) / 8(sm), 안쪽 여백 4(md) / 3(sm) (ToggleGroup 과 같은 값)
  • 선택된 항목은 surface-raised 면 + ToggleGroup 과 같은 그림자(CoreShadow.xs) + 굵은(semibold) 텍스트로 강조
  • reset 버튼이 보일 때만 항목과 reset 버튼 사이에 1px · 높이 16(md) / 14(sm) outline-variant 구분선
  • 비활성: 트랙 disabled-container, 글자 disabled-content, 선택 면 surface-container-high 에 그림자 없음
  • 선택 전환 시 150ms 색상 transition

레이아웃#

  • 항목들은 가로로 배치되며 itemSpacing 간격 유지
  • 각 세그먼트는 높이 itemHeight(md 32 / sm 26) 고정, 최소 폭은 그 높이. 칸 좌우 여백(md 2 / sm 0) 안에 [아이콘] + [라벨 박스(labelPadding 가로 8)]를 가로 · 세로 가운데에 배치 — 아이콘 전용 세그먼트는 md 32×32 · sm 26×26 정사각, 라벨 전용 세그먼트는 md 텍스트 폭 + 20 · sm 텍스트 폭 + 16
  • 트랙 높이는 md 4 + 32 + 4 = 40, sm 3 + 26 + 3 = 32 — 같은 줄의 같은 크기 컨트롤과 맞습니다. 밀도는 트랙을 움직이지 않습니다(md 는 모든 밀도에서 40)
  • showResetButton: true이고 onReset != null이면 트랙 안 항목들 뒤에 구분선과 X 아이콘 reset 버튼 표시 — Button(variant: .ghost, size: .sm, shape: .square) (md 32×32 · sm 폭 32 × 높이 26)

사용 가이드라인 (Usage Guidelines)#

✅ Do#

선택 해제가 유효한 상태라면 showResetButton과 onReset을 함께 제공

Filter<String>(
  groupValue: _selected,
  onValueChanged: (v) => setState(() => _selected = v),
  onReset: () => setState(() => _selected = null),
  showResetButton: true,
  items: items,
)

리셋 아이콘 버튼은 showResetButton과 onReset이 모두 있어야만 렌더링됩니다 — 하나만 설정하면 리셋 기능이 화면에 나타나지 않습니다.


❌ Don't#

다중 선택에 Filter를 사용하지 않기

// ❌ Filter 는 controlled 단일 선택 전용 — 여러 값을 동시에 선택할 수 없음
Filter<String>(
  groupValue: _selected, // T? — 단일 값만 보유
  onValueChanged: (v) => setState(() => _selected = v),
  items: items,
)

groupValue는 T? 단일 값이라 라디오 그룹처럼 상호 배타적으로만 동작합니다. 다중 선택이 필요하면 ChipGroup을 사용하세요.

접근성 (Accessibility)#

역할#

  • Web: 합성한 ToggleGroup 의 트랙이 role="radiogroup", 각 항목이 role="radio" + aria-checked + aria-label=item.label 입니다. 비활성이면 그룹에 aria-disabled="true" 가 붙고 항목은 탭 정지점이 없습니다. 폭 고정용 고스트 라벨은 aria-hidden="true" 로 빠집니다. 리셋 버튼은 aria-label=filterResetLabel 입니다.
  • Flutter: 각 항목이 Semantics(checked:, inMutuallyExclusiveGroup: true, enabled:, label:) 입니다. 합성한 Clickable 은 버튼 역할을 내려놓습니다. 고스트 라벨은 Visibility(visible: false) 의 기본 maintainSemantics: false 로 트리에서 빠집니다. 리셋 버튼은 Semantics(button: true, label: filterResetLabel) 로 이름이 있습니다.

키보드#

키FlutterWeb
Tab 리셋 버튼과 모든 항목에 하나씩 정지 리셋 버튼 + 그룹의 항목 하나(선택된 항목, 없으면 첫 항목)
Enter / Space 포커스된 항목 선택 / 리셋 실행 포커스된 항목 선택 / 리셋 실행
← / ↑, → / ↓ 프레임워크 방향성 포커스 이동 (선택은 바꾸지 않음) 이전/다음 항목으로 이동하고 선택, 끝에서 순환
Home / End없음첫/마지막 항목으로 이동하고 선택

리셋 버튼은 항목들 뒤에 렌더되므로 Tab 순서상 마지막 정지점입니다.

Web 은 WAI-ARIA radio group 패턴의 roving tabindex 를 구현합니다. 화살표· Home/End 는 포커스뿐 아니라 선택도 바꿉니다. Flutter 의 방향성 순회는 프레임워크 폴백이고 라디오 그룹의 선택 이동이 아닙니다.

브라우저 검증 — Web 키보드 프로브(tool/parity/keyboard_probe.dart)가 하이드레이션된 문서 사이트에서 실제 포커스 이동 결과를 확인합니다.

스크린 리더#

각 항목의 접근 가능한 이름은 item.label 입니다 (Web aria-label, Flutter Semantics(label:)). showLabel: false 인 아이콘 전용 세그먼트도 이름은 유지됩니다. 선택 상태는 Web aria-checked, Flutter Semantics(selected:) 로 전달됩니다. 리셋 버튼의 이름은 로컬라이즈된 filterResetLabel(영문 "Reset filter", 한국어 "필터 초기화")입니다.

그룹 자체는 이름이 없습니다 — 어느 플랫폼도 루트에 라벨을 붙이지 않습니다. Web 은 role="radiogroup" 을 이름 없이 내보내고 Flutter 는 그룹 노드가 아예 없어서, "무엇을 고르는 필터인가" 가 전달되지 않습니다.

live region 은 없습니다. 선택 변경은 포커스가 그 항목에 있을 때 상태 변화로만 전달되고, 별도 통보는 발생하지 않습니다.

포커스 관리#

오버레이가 아니므로 진입·이탈 시 포커스 이동이 없고 트랩도 없습니다. 포커스 표시는 합성한 Clickable 의 기본 링이 항목마다 그립니다. 선택이 바뀌어도 포커스는 방금 활성화한 항목에 그대로 남습니다 — 두 구현 모두 onValueChanged 만 호출하고 포커스를 옮기지 않습니다.

알려진 제약#

  • Flutter 는 항목마다 tab stop 이 생깁니다. Web 의 radio group 키보드 패턴과 달리 화살표가 선택을 바꾸지 않습니다. 항목을 선택하려면 Enter / Space 를 씁니다.
  • 그룹에 이름을 줄 방법이 Web 에 없습니다 — Filter 생성자가 super.attributes / super.classes / super.css 를 받지 않아 aria-label / aria-labelledby 를 루트에 실을 진입점이 없습니다. 대안: Flutter 는 호출부가 Semantics(container: true, label: …) 로 감쌀 수 있고, Web 은 필터 옆에 보이는 제목 텍스트를 배치하는 우회가 전부입니다.
  • 개별 항목을 비활성화하거나 이름을 따로 줄 수 없습니다 — CoreFilterItem 의 icon / showLabel 은 표시를 조절하지만 비활성 상태나 별도 접근성 이름을 받지 않습니다. 고를 수 없는 값은 items 에서 제외합니다.

이 컴포넌트가 직접 다루지 않는 대비·모션 등 전역 축은 전역 접근성 축 에서 한 곳으로 관리됩니다.

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
선택 의미각 Clickable의 label·selected 시맨틱radiogroup / radio · aria-checked
키보드항목별 Tab · Enter / Spaceroving tabindex · 양 축 화살표 · Home / End로 선택 이동
색 전환AnimatedContainerCSS transition
폭 유지숨긴 semibold 텍스트를 크기만 유지aria-hidden 라벨 ghost