MultipleChoice | CoUI
LogoCoUI

MultipleChoice

여러 옵션 중 하나를 선택하는 단일 선택 그룹 컴포넌트

MultipleChoice#

여러 옵션을 나열하고 그중 하나만 선택할 수 있는 단일 선택 그룹 컴포넌트입니다. allowUnselect가 true이면 선택된 옵션을 다시 눌러 선택을 해제할 수 있습니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 크기 / 옵션처럼 적은 수의 후보 중 하나를 버튼 형태로 선택할 때
  • 라디오 버튼보다 강조된 선택 UI가 필요할 때

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

  • RadioGroup: 전통적인 라디오 버튼 형태가 필요할 때
  • Select: 옵션이 많아 드롭다운이 적합할 때

기본 사용법 (Basic Usage)#

MultipleChoice<String>(
  options: const [
    (value: 'sm', label: 'Small'),
    (value: 'md', label: 'Medium'),
    (value: 'lg', label: 'Large'),
  ],
  value: size,
  onChanged: (v) => setState(() => size = v),
)
MultipleChoice<String>(
  options: const [
    (value: 'sm', label: 'Small'),
    (value: 'md', label: 'Medium'),
    (value: 'lg', label: 'Large'),
  ],
  value: size,
  onChanged: (v) => setState(() => size = v),
)

빠른 오버라이드 (Chain)#

이미 만든 MultipleChoice 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

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

  @override
  State<MultipleChoiceChainExample> createState() => _MultipleChoiceChainExampleState();
}

class _MultipleChoiceChainExampleState extends State<MultipleChoiceChainExample> {
  String? _value = 'md';

  @override
  Widget build(BuildContext context) {
    return MultipleChoice<String>(
      options: const [
        (value: 'sm', label: 'Small'),
        (value: 'md', label: 'Medium'),
        (value: 'lg', label: 'Large'),
      ],
      value: _value,
      onChanged: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreMultipleChoiceStyle(
        optionPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space20,
          vertical: CoreSpace.space12,
        ),
        optionBorderRadius: CoreBorderRadius.all(CoreRadius.radius16),
        optionBorderWidth: CoreStrokeWidth.stroke2,
        selectedBackgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
        selectedBorderColor: CoreColor.token(CoreColors.tertiary),
      ),
    );
  }
}
class MultipleChoiceChainExample extends StatefulComponent {
  const MultipleChoiceChainExample({super.key});

  @override
  State<MultipleChoiceChainExample> createState() => _MultipleChoiceChainExampleState();
}

class _MultipleChoiceChainExampleState extends State<MultipleChoiceChainExample> {
  String? _value = 'md';

  @override
  Component build(BuildContext context) {
    return MultipleChoice<String>(
      options: const [
        (value: 'sm', label: 'Small'),
        (value: 'md', label: 'Medium'),
        (value: 'lg', label: 'Large'),
      ],
      value: _value,
      onChanged: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreMultipleChoiceStyle(
        optionPadding: CoreEdgeInsets.symmetric(
          horizontal: CoreSpace.space20,
          vertical: CoreSpace.space12,
        ),
        optionBorderRadius: CoreBorderRadius.all(CoreRadius.radius16),
        optionBorderWidth: CoreStrokeWidth.stroke2,
        selectedBackgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
        selectedBorderColor: CoreColor.token(CoreColors.tertiary),
      ),
    );
  }
}

Props / Parameters#

속성타입기본값설명
options List<({T value, String label})> 필수 선택 가능한 옵션 목록
valueT?null현재 선택된 값
enabledbooltrue선택 가능 여부
allowUnselect bool false 선택 항목 재탭으로 해제 허용
onChanged ValueChanged<T?>? null 선택 변경 콜백
multipleChoiceStyle CoreMultipleChoiceStyle? null 옵션 chrome 오버라이드

스타일 시스템 (Style System)#

CoreMultipleChoiceStyle 필드#

필드타입설명
optionSpacing double? Nested [CoreGapStyle] slot for the gap between option buttons. Forwarded to the Gap siblings rendered between consecutive options (Flutter) or used to drive the root row gap CSS (Web). Merged on top of [defaultOptionSpacing].
optionPadding CoreEdgeInsets? Option padding override.
optionBorderRadius CoreBorderRadius? Option corner radius override.
optionBorderWidth double? Option border width override (logical px).
selectedBackgroundColor CoreColor? Selected option background colour override.
selectedTextStyle CoreTextStyle? Selected option text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw selectedTextColor field removed). Defaults to [defaultSelectedTextStyle].
selectedBorderColor CoreColor? Selected option border colour override.
unselectedBackgroundColor CoreColor? Unselected option background colour override.
unselectedTextStyle CoreTextStyle? Unselected option text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw unselectedTextColor field removed). Defaults to [defaultUnselectedTextStyle].
unselectedBorderColor CoreColor? Unselected option border colour override.
disabledTextStyle CoreTextStyle? Disabled option text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw disabledTextColor field removed). Defaults to [defaultDisabledTextStyle].
transitionDuration Duration? Transition duration override for selection colour changes.
clickableStyle CoreClickableStyle? Nested [CoreClickableStyle] slot for the composed per-option Clickable (press scale / durations / focus ring / disabled opacity). Merged on top of [defaultClickableStyle] and raw-forwarded — the Clickable's own resolver fills the rest.

동작 스펙 (Behavior)#

  • 선택: 옵션 클릭 시 해당 값으로 onChanged 호출
  • 해제: allowUnselect가 true일 때 선택된 옵션 재클릭 → onChanged(null)
  • 단일 선택: 항상 최대 한 개만 선택됨

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

✅ Do#

선택 해제가 의미 있을 때만 allowUnselect 사용

MultipleChoice<String>(
  options: const [
    (value: 'all', label: 'All'),
    (value: 'active', label: 'Active'),
  ],
  value: filter,
  allowUnselect: true,
  onChanged: (v) => setState(() => filter = v),
)

allowUnselect 기본값은 false 라 선택된 옵션을 다시 눌러도 해제되지 않습니다. "필터 초기화"처럼 선택 해제 자체가 유효한 상태일 때만 켜세요.


❌ Don't#

옵션별 개별 비활성화를 흉내내지 않기

// ❌ enabled 는 그룹 전체에만 적용 — 특정 옵션만 막을 수 없음
MultipleChoice<String>(
  options: const [
    (value: 'sm', label: 'Small'),
    (value: 'md', label: 'Medium (품절)'),
  ],
  value: size,
  enabled: size != 'md',
)

CoreMultipleChoiceOption 레코드에는 per-option disabled 필드가 없습니다 — enabled 는 그룹 전체를 한꺼번에 켜고 끌 뿐입니다. 특정 옵션만 막아야 한다면 options 목록에서 그 옵션을 아예 제외하세요.

접근성 (Accessibility)#

  • 컨테이너: role="radiogroup"
  • 각 옵션: role="radio" + aria-checked

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

항목FlutterWeb
클래스명 MultipleChoice<T> MultipleChoice<T>
옵션 렌더 GestureDetector + AnimatedContainer <button>