MultipleChoice#
여러 옵션을 나열하고 그중 하나만 선택할 수 있는 단일 선택 그룹 컴포넌트입니다. allowUnselect가 true이면 선택된 옵션을 다시 눌러 선택을 해제할 수 있습니다.
Live Preview#
default
Web
Small
Medium
Large
Flutter
Loading Flutter...
class MultipleChoiceDefaultExample extends StatefulComponent {
const MultipleChoiceDefaultExample({super.key});
@override
State<MultipleChoiceDefaultExample> createState() =>
_MultipleChoiceDefaultExampleState();
}
class _MultipleChoiceDefaultExampleState
extends State<MultipleChoiceDefaultExample> {
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),
);
}
}
class MultipleChoiceDefaultExample extends StatefulWidget {
const MultipleChoiceDefaultExample({super.key});
@override
State<MultipleChoiceDefaultExample> createState() =>
_MultipleChoiceDefaultExampleState();
}
class _MultipleChoiceDefaultExampleState
extends State<MultipleChoiceDefaultExample> {
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),
);
}
}
chain
Web
Small
Medium
Large
Flutter
Loading Flutter...
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),
),
);
}
}
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),
),
);
}
}
사용 시기 (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})> |
필수 | 선택 가능한 옵션 목록 |
value | T? | null | 현재 선택된 값 |
enabled | bool | true | 선택 가능 여부 |
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)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | MultipleChoice<T> |
MultipleChoice<T> |
| 옵션 렌더 | GestureDetector + AnimatedContainer |
<button> |
관련 컴포넌트 (Related Components)#
- RadioGroup: 라디오 버튼 단일 선택
- Select: 드롭다운 선택
- ChipGroup: 칩 형태 다중 선택