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

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#

구조 (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입니다.

속성타입기본값설명
labelW필수선택지의 이름
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 — 줄이 목록의 가장자리에 붙습니다
간격spacing12 — 앞 글리프, 글, 표시 사이
라벨 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 라 탭 순서와 보조 기술에서 빠집니다

키보드#

키FlutterWeb
Tab활성 줄에 포커스 이동활성 줄에 포커스 이동
Enter / Space줄을 탭한 것과 같음줄을 탭한 것과 같음

스크린 리더#

라벨, 설명 순서로 읽힙니다. 고른 상태는 Flutter 에서 selected, Web 에서 aria-current 로 전해집니다.

포커스 관리#

  • 진입 · 이탈 · 트랩: 없음 — 줄은 오버레이가 아닙니다

알려진 제약#

  • Web 은 고른 상태를 aria-current 로 전하므로, 라디오나 체크박스 그룹 역할이 필요한 폼에서는 RadioGroup · Checkbox 를 씁니다
  • multiple 의 체크박스가 탭 순서에서 빠지는 것은 브라우저에서 확인하지 않았습니다

전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.

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

항목FlutterWeb
클래스명OptionItemOptionItem
루트 Clickable → Stacks(눌림 면 + Padding → Row) Clickable 의 <div> 안에 눌림 면 · 글 · 표시
눌림 면 눌린 동안에만 Position 으로 줄 박스에 묶은 DecoratedBox 항상 있는 면 <div> 를 :active 규칙이 보이게 합니다
눌림 감지Clickable.onPressedChangeCSS :active
multiple 표시 Checkbox 를 IgnorePointer · ExcludeFocus · ExcludeSemantics 로 감쌉니다 Checkbox 를 inert · pointer-events-none 이 붙은 <div> 로 감쌉니다
고른 상태 전달semanticsSelectedaria-current="true"
Web 전용 HTML 표면 — attributes · classes · css · eventHandlers · id 통과

위 차이 밖에는 없습니다 — 이름 · 순서 · 기본값을 두 위젯 생성자에서 대조했습니다.

  • OptionList: 줄을 세로로 쌓는 묶음
  • Checkbox: multiple 의 표시
  • MenuItem: 선택이 아니라 동작을 실행하는 메뉴 줄
  • Select: 목록을 드롭다운 안에 접는 선택

조합 예제#

OptionList(
  children: [
    OptionItem(label: const Text('최신순'), selected: true, onPressed: () {}),
    OptionItem(label: const Text('인기순'), onPressed: () {}),
  ],
)