OptionItem
선택지 한 줄 — 단일 선택은 체크, 다중 선택은 체크박스로 고른 상태를 말합니다
목차
OptionItem 은 선택지 목록의 한 줄입니다. 정렬 기준이나 색상처럼 바텀시트 · 패널 안에서 하나 또는 여럿을 고르는
목록의 각 줄이 이 컴포넌트입니다. 줄은 무엇이 골라졌는지 스스로 들고 있지 않습니다 — selected 가 그 상태를
말하고 onPressed 가 탭을 알릴 뿐이며, 어느 줄이 골라졌는지는 호출자가 정합니다.
기본 사용법 (Basic Usage)#
OptionItem(
label: const Text('최신순'),
selected: true,
onPressed: () => setState(() => sort = Sort.latest),
)
OptionItem(
label: const Text('최신순'),
selected: true,
onPressed: () => setState(() => sort = Sort.latest),
)
고른 줄을 정하는 것은 호출자입니다. 줄을 탭하면 onPressed 만 불리고 selected 는 저절로 바뀌지 않습니다.
enabled 를 주지 않으면 onPressed 가 있는지로 정해집니다 — 핸들러가 없는 줄은 비활성입니다.
Live Preview#
코드 보기
class OptionItemDefaultExample extends StatelessComponent {
const OptionItemDefaultExample({super.key});
@override
Component build(BuildContext context) {
return OptionItem(
label: const Text('최신순'),
selected: true,
onPressed: () {},
);
}
}
class OptionItemDefaultExample extends StatelessWidget {
const OptionItemDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return OptionItem(
label: const Text('최신순'),
selected: true,
onPressed: () {},
);
}
}
구조 (Anatomy)#
앞에서 뒤로 앞 글리프, 글, 표시 순서이고, 자리 사이는 spacing 입니다. 눌린 동안에는 줄의 좌우 바깥으로 나가는
둥근 면이 글과 표시 위에 깔립니다.
[ leading ] [ label ] [ mark ]
[ description ]
| 부분 | 역할 | 슬롯 |
|---|---|---|
| 앞 글리프 | 라벨 앞의 작은 그림 | leading |
| 라벨 | 선택지의 이름 | label |
| 설명 | 라벨 아래 짧은 한 줄 | description |
| 표시 | 고른 상태 — single 은 고른 줄 끝의 체크, multiple 은 모든 줄 끝의 Checkbox |
selection · selected |
| 눌림 면 | 눌린 동안 줄 바깥으로 나가는 둥근 면 |
Figma: CoUI 라이브러리 파일의
OptionItem 페이지
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 바텀시트나 패널 안에서 목록으로 하나 또는 여럿을 고를 때 (정렬 기준, 필터 값)
- 줄마다 짧은 설명이나 앞 글리프를 함께 보여 줄 때
대신 다른 컴포넌트를 사용하세요:
- Select: 목록을 트리거의 드롭다운 안에 접어 둘 때
- RadioGroup: 폼 안에서 라디오 표시로 하나를 고를 때
- Checkbox: 목록이 아니라 단독 확인 항목일 때
- MenuItem: 선택이 아니라 동작을 실행하는 메뉴 줄일 때
Props / Parameters#
W 는 Flutter의 Widget / Web의 Jaspr Component입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
label | W | 필수 | 선택지의 이름 |
description |
W? |
null |
라벨 아래의 짧은 한 줄. 없으면 그 줄을 그리지 않습니다 |
leading |
W? |
null |
라벨 앞의 글리프. 없으면 그 자리를 그리지 않습니다 |
selection |
CoreOptionItemSelection |
CoreOptionItemSelection.defaultSelection(single) |
고른 상태를 보이는 방식 —
single
은 고른 줄 끝에 체크,
multiple
은 모든 줄 끝에
Checkbox
|
selected |
bool |
false |
이 줄이 골라졌는지 |
enabled |
bool? |
null |
탭을 받는지. null 이면 onPressed 가 있는지로 정해집니다 |
onPressed |
void Function()? |
null |
줄을 탭했을 때 불립니다 |
optionItemStyle |
CoreOptionItemStyle? |
null |
모든 chrome 이 지나는 단일 슬롯 (아래 표 참고) |
CoreOptionItemStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
padding |
CoreEdgeInsets? |
Inset of the row's content (pre-scaling). null → [defaultPadding]. |
spacing |
double? |
Space between the seats of the row — leading, text, mark (logical px, pre-scaling).
null
→ [defaultSpacing].
|
labelTextStyle |
CoreTextStyle? |
Text style of the label, merged on top of [defaultLabelTextStyle]. |
selectedLabelTextStyle |
CoreTextStyle? |
What a chosen row's label states over [labelTextStyle] — its colour and weight.
null
→ the entry of [defaultsBySelection] for the row's selection.
|
descriptionTextStyle |
CoreTextStyle? |
Text style of the description, merged on top of [defaultDescriptionTextStyle]. |
descriptionGapStyle |
CoreGapStyle? |
Space between the label and the description. null → [defaultDescriptionGapStyle]. |
leadingIconStyle |
CoreIconStyle? |
Size of the leading glyph. null → [defaultLeadingIconStyle]. |
checkIconStyle |
CoreIconStyle? |
Size and colour of the check mark of a chosen single-selection row.
null
→ [defaultCheckIconStyle].
|
disabledForegroundColor |
CoreColor? |
Colour of the label, description, leading glyph and check mark of a disabled row.
null
→ [defaultDisabledForegroundColor].
|
pressedBackgroundColor |
CoreColor? |
The colour of the pressed face — shown at [stateLayer]'s pressed weight.
null
→ [defaultPressedBackgroundColor].
|
stateLayer |
CoreStateLayer? |
How strongly the pressed face shows. null → [defaultStateLayer]. |
pressedBackgroundBorderRadius |
CoreBorderRadius? |
Corner of the pressed face. null → [defaultPressedBackgroundBorderRadius]. |
pressedBackgroundOutset |
double? |
How far the pressed face reaches past each side of the row (logical px, pre-scaling).
null
→ [defaultPressedBackgroundOutset].
|
pressedBackgroundInset |
double? |
How far the pressed face stops short of the row's top and bottom (logical px, pre-scaling).
null
→ [defaultPressedBackgroundInset].
|
clickableStyle |
CoreClickableStyle? |
Nested chrome of the composed
Clickable
(focus ring, press feedback).
null
→ [defaultClickableStyle]. Raw-forwarded.
|
스타일 시스템 (Style System)#
optionItemStyle#
-
시맨틱 / behaviour: 위젯 파라미터로 직접 (
selection·selected·enabled·onPressed) - chrome / dimensional:
CoreOptionItemStyle한 곳으로
| 부분 | 필드 | 기본값 |
|---|---|---|
| 여백 | padding | 위아래 14 · 좌우 0 — 줄이 목록의 가장자리에 붙습니다 |
| 간격 | spacing | 12 — 앞 글리프, 글, 표시 사이 |
| 라벨 | labelTextStyle |
bodyLarge · onSurface |
| 고른 라벨 | selectedLabelTextStyle |
selection
별 기본값 —
single
은
primary
,
multiple
은
onSurface
, 둘 다
medium
굵기
|
| 설명 | descriptionTextStyle · descriptionGapStyle |
bodySmall · onSurfaceVariant, 라벨과 간격 2 |
| 아이콘 | leadingIconStyle · checkIconStyle |
앞 글리프 20, 체크 20 · primary |
| 비활성 | disabledForegroundColor |
disabledContent — 라벨 · 설명 · 글리프 · 체크의 색 |
| 눌림 면 |
pressedBackgroundColor
·
stateLayer
·
pressedBackgroundBorderRadius
|
onSurface · CoreStateLayer.subtle 의 눌림 몫 · 모서리 12 |
| 눌림 면 위치 | pressedBackgroundOutset · pressedBackgroundInset |
좌우로 12 나가고 위아래는 2 못 미칩니다 |
| 눌림 반응 | clickableStyle |
눌림 확대 없음, 비활성 흐림 없음 — 눌림 면이 그 반응이고 비활성 색은 이미 disabledContent 입니다 |
줄의 높이는 필드가 아니라 내용이 정합니다. 위아래 여백 14 와 bodyLarge 한 줄(24)이 더해져 52 이고, 설명이 있으면
설명 줄과 간격만큼 커집니다. 여백 · 간격은 간격 사다리 위의 값이라 밀도(CoreDensity)가 한 칸 옮깁니다.
Resolve chain
CoreOptionItemStyle.defaultX // 기본값 (+ defaultsBySelection)
→ CoreOptionItemTheme.style // 프로젝트 공통
→ widget.optionItemStyle // 인스턴스별
사용 예
CoreComponentTheme(
optionItem: CoreOptionItemTheme(
style: CoreOptionItemStyle(
pressedBackgroundBorderRadius: CoreBorderRadius.all(CoreRadius.radius8),
),
),
)
빠른 오버라이드 (Chain)
이미 만든 OptionItem 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다.
생성자의 style 슬롯 인자와 동일하게 동작합니다.
OptionItem(
label: const Text('최신순'),
selected: true,
onPressed: () {},
).withStyle(
const CoreOptionItemStyle(spacing: CoreSpace.space8),
)
변형 (Variants)#
selection: multiple#
모든 줄 끝에 Checkbox(lg)가 놓입니다. 고른 줄 라벨은 onSurface 그대로 굵기만 medium
이 되고, 고른 상태는
체크박스가 말합니다. 체크박스는 줄 상태의 그림일 뿐 입력을 받지 않습니다 — 탭은 줄이 받습니다.
OptionItem(
selection: .multiple,
label: const Text('무료 배송'),
description: const Text('3만원 이상 주문'),
selected: true,
onPressed: toggleFreeShipping,
)
비활성#
enabled: false 이거나 onPressed 가 없는 줄입니다. 라벨 · 설명 · 글리프 · 체크가 disabledContent
색이 되고
탭에 반응하지 않습니다.
OptionItem(
label: const Text('품절된 옵션'),
description: const Text('재고가 없어요'),
enabled: false,
)
동작 스펙 (Behavior)#
배치#
- 앞 글리프, 글, 표시 순서의 한 줄이고 글이 남는 폭을 채웁니다. 설명이 없으면 세로 가운데에, 있으면 위에 맞춥니다
- 줄은 받은 폭을 채우고, 높이는 내용이 정합니다 (설명이 없을 때 52)
single은 고른 줄에만 끝 체크를 그립니다. 고르지 않은 줄에는 표시 자리가 없습니다multiple은 모든 줄에 체크박스를 그립니다
인터랙션#
- 탭하면
onPressed가 불리고, 줄은selected를 바꾸지 않습니다 - 누르고 있는 동안 눌림 면이 깔립니다. 줄의 좌우로 12 나가고 위아래는 2 못 미치는 모서리 12 의 면이며, 글과 표시 위에 그려집니다
- 비활성 줄은 눌림 면을 그리지 않고, 포인터는 금지 모양(Flutter)이 됩니다
상태 전환#
| 상태 | 라벨 | 표시 |
|---|---|---|
| 기본 | onSurface |
single 은 없음, multiple 은 빈 체크박스 |
| 고름 |
single
은
primary
·
medium
,
multiple
은
onSurface
·
medium
|
single 은 체크, multiple 은 켜진 체크박스 |
| 눌림 | 눌림 면이 깔림 | |
| 비활성 | disabledContent |
single 의 체크도 disabledContent, multiple 은 비활성 체크박스 |
애니메이션#
- 눌림 면이 생기고 사라지는 전환 애니메이션은 없습니다
사용 가이드라인 (Usage Guidelines)#
Do#
고른 상태는 호출자가 들고, 줄은 보이기만 한다
OptionList(
children: [
for (final sort in Sort.values)
OptionItem(
label: Text(sort.label),
selected: sort == current,
onPressed: () => setState(() => current = sort),
),
],
)
이유: 줄이 선택을 스스로 들면 목록의 다른 줄과 어긋납니다. 단일 선택에서 한 줄만 고르는 규칙은 목록을 아는 호출자의 몫입니다.
Don't#
줄 안에 또 다른 행동을 넣지 않는다
// ❌ 줄 전체가 탭 영역인데 안에 버튼이 있으면 어느 쪽이 눌리는지 모호하다
OptionItem(
label: Row(children: [const Text('최신순'), Button(onPressed: more, child: const Text('더보기'))]),
onPressed: select,
)
이유: 줄은 하나의 탭 대상입니다. 추가 행동이 필요하면 목록 밖으로 뺍니다.
접근성 (Accessibility)#
역할#
-
Flutter: 줄은
Clickable이 버튼 역할로 노출하고selected·enabled를 시맨틱에 싣습니다.multiple의 체크박스는 시맨틱 트리에서 빠집니다 — 줄이 선택 상태를 말합니다 -
Web: 줄은
Clickable이role="button"으로 만든<div>이고 고른 줄에aria-current="true", 비활성 줄에aria-disabled="true"를 내보냅니다.multiple의 체크박스 감싸개는inert라 탭 순서와 보조 기술에서 빠집니다
키보드#
| 키 | Flutter | Web |
|---|---|---|
Tab | 활성 줄에 포커스 이동 | 활성 줄에 포커스 이동 |
Enter / Space | 줄을 탭한 것과 같음 | 줄을 탭한 것과 같음 |
스크린 리더#
라벨, 설명 순서로 읽힙니다. 고른 상태는 Flutter 에서 selected, Web 에서 aria-current 로 전해집니다.
포커스 관리#
- 진입 · 이탈 · 트랩: 없음 — 줄은 오버레이가 아닙니다
알려진 제약#
-
Web 은 고른 상태를
aria-current로 전하므로, 라디오나 체크박스 그룹 역할이 필요한 폼에서는RadioGroup·Checkbox를 씁니다 multiple의 체크박스가 탭 순서에서 빠지는 것은 브라우저에서 확인하지 않았습니다
전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | OptionItem | OptionItem |
| 루트 | Clickable → Stacks(눌림 면 + Padding → Row) |
Clickable 의 <div> 안에 눌림 면 · 글 · 표시 |
| 눌림 면 | 눌린 동안에만 Position 으로 줄 박스에 묶은 DecoratedBox |
항상 있는 면 <div> 를 :active 규칙이 보이게 합니다 |
| 눌림 감지 | Clickable.onPressedChange | CSS :active |
multiple 표시 |
Checkbox
를
IgnorePointer
·
ExcludeFocus
·
ExcludeSemantics
로 감쌉니다
|
Checkbox
를
inert
·
pointer-events-none
이 붙은
<div>
로 감쌉니다
|
| 고른 상태 전달 | semanticsSelected | aria-current="true" |
| Web 전용 HTML 표면 | — |
attributes
·
classes
·
css
·
eventHandlers
·
id
통과
|
위 차이 밖에는 없습니다 — 이름 · 순서 · 기본값을 두 위젯 생성자에서 대조했습니다.
관련 컴포넌트 (Related Components)#
- OptionList: 줄을 세로로 쌓는 묶음
- Checkbox:
multiple의 표시 - MenuItem: 선택이 아니라 동작을 실행하는 메뉴 줄
- Select: 목록을 드롭다운 안에 접는 선택
조합 예제#
OptionList(
children: [
OptionItem(label: const Text('최신순'), selected: true, onPressed: () {}),
OptionItem(label: const Text('인기순'), onPressed: () {}),
],
)