Collapsible | CoUI
LogoCoUI

Collapsible

클릭으로 내용을 접거나 펼칠 수 있는 토글 가능한 영역 컴포넌트

Collapsible#

트리거를 클릭하여 내용을 접거나 펼칠 수 있는 토글 영역 컴포넌트입니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 보조 정보나 상세 내용을 기본적으로 숨겨두고 필요 시 펼쳐 보여줄 때
  • 단일 섹션을 접기/펼치기로 제어할 때 (여러 섹션 관리는 Accordion 사용)
  • 필터 패널, 추가 옵션, 긴 약관 내용 등을 토글로 보여줄 때

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

  • Accordion: 여러 개의 접기/펼치기 패널을 하나의 그룹으로 관리할 때
  • Tree: 계층 구조의 데이터를 트리 형태로 표시할 때

기본 사용법 (Basic Usage)#

// 기본 접기/펼치기
Collapsible(
  trigger: Text('상세 정보'),
  content: Text('여기에 상세 내용이 표시됩니다.'),
)

// 초기 펼침 상태
Collapsible(
  trigger: Text('약관 내용'),
  content: TermsContent(),
  expanded: true,
)

// 외부 제어
Collapsible(
  trigger: Text('필터 옵션'),
  content: FilterPanel(),
  expanded: isFilterExpanded,
  onExpandedChanged: (value) => setState(() => isFilterExpanded = value),
)

Props / Parameters#

속성타입기본값설명
trigger Widget / Component 필수 클릭 가능한 헤더
content Widget / Component 필수 펼쳤을 때 표시할 내용
expandedboolfalse초기 펼침 상태
onExpandedChanged void Function(bool)? null 상태 변경 콜백
collapsibleStyle CoreCollapsibleStyle? null chrome 단일 진입점 (아래 표 참고)

CoreCollapsibleStyle 필드#

필드타입설명
contentInset CoreEdgeInsets? Inner inset around the trigger and the content area (pre-scaling).
duration Duration? Expand/collapse animation duration.
triggerTextStyle CoreTextStyle? Trigger row text style override.
contentTextStyle CoreTextStyle? Content body text style override. Layered over the bodyMedium typography role + on-surface colour the widget composes for the expanded content area (single static site, no state branch).
iconLabelGapStyle CoreGapStyle? Horizontal gap slot between the trigger text and the chevron icon. Forwarded straight to Gap(gapStyle: …) (Flutter) and to the gap <div> width (Web).
iconStyle CoreIconStyle? Chevron / disclosure icon style.
clickableStyle CoreClickableStyle? Nested [CoreClickableStyle] slot for the composed trigger Clickable (press scale / focus ring / transition durations). Merged on top of [defaultClickableStyle] and raw-forwarded — the Clickable's own resolver fills the remaining defaults.

빠른 오버라이드 (Chain)#

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

class CollapsibleChainExample extends StatelessWidget {
  const CollapsibleChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const Collapsible(
      trigger: Text('Toggle Content'),
      content: Text(
        'This is the collapsible content that can be expanded or collapsed.',
      ),
      expanded: true,
    ).withStyle(
      const CoreCollapsibleStyle(
        contentInset: CoreEdgeInsets.all(CoreSpace.space24),
        duration: Duration(milliseconds: CoreDuration.moderate),
        triggerTextStyle: CoreTextStyle.token(
          CoreTextStyles.titleSmall,
          color: CoreColor.token(CoreColors.primary),
        ),
        iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space12),
        iconStyle: CoreIconStyle(
          size: CoreSize.size20,
          color: CoreColor.token(CoreColors.primary),
        ),
      ),
    );
  }
}
class CollapsibleChainExample extends StatelessComponent {
  const CollapsibleChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return Collapsible(
      trigger: Text('Toggle Content'),
      content: Text(
        'This is the collapsible content that can be expanded or collapsed.',
      ),
      expanded: true,
    ).withStyle(
      const CoreCollapsibleStyle(
        contentInset: CoreEdgeInsets.all(CoreSpace.space24),
        duration: Duration(milliseconds: CoreDuration.moderate),
        triggerTextStyle: CoreTextStyle.token(
          CoreTextStyles.titleSmall,
          color: CoreColor.token(CoreColors.primary),
        ),
        iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space12),
        iconStyle: CoreIconStyle(
          size: CoreSize.size20,
          color: CoreColor.token(CoreColors.primary),
        ),
      ),
    );
  }
}

변형 (Variants)#

기본 (닫힘 상태로 시작)#

Collapsible(
  trigger: Text('추가 옵션'),
  content: AdditionalOptions(),
)

기본 펼침#

Collapsible(
  trigger: Text('기본 정보'),
  content: UserInfoPanel(),
  expanded: true,
)

동작 스펙 (Behavior)#

인터랙션#

  • 클릭/탭: 트리거 영역 클릭 시 펼침/접힘 토글, onExpandedChanged 호출
  • 호버: 트리거 텍스트 밑줄 표시
  • 포커스: 트리거에 포커스 시 Enter/Space로 토글 가능

상태 전환#

  • collapsedexpanded: 트리거 클릭 시 내용 영역 펼침
  • expandedcollapsed: 트리거 재클릭 시 내용 영역 접음
  • 화살표 아이콘이 펼침/닫힘 상태에 따라 180도 회전

애니메이션#

  • 높이 전환: 200ms ease-in-out (CoreDuration.normal)
  • 화살표 아이콘 회전: 200ms ease-in-out

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

✅ Do#

보조적인 내용을 기본 숨김 상태로 시작

Collapsible(
  trigger: Text('고급 설정'),
  content: AdvancedSettingsPanel(),
  // expanded 기본값 false
)

대부분의 사용자에게 불필요한 정보를 숨겨 페이지를 깔끔하게 유지한다.


❌ Don't#

필수 정보를 Collapsible 안에 숨기지 않기

// ❌ 중요한 오류 메시지를 접힌 상태로 숨김
Collapsible(
  trigger: Text('결제 오류'),
  content: Text('카드 정보가 유효하지 않습니다.'),
)

사용자가 중요한 정보를 놓칠 수 있으며, Alert나 Dialog로 즉시 표시해야 한다.

✅ Do#

여러 독립 섹션은 각각 별도 Collapsible로 관리

Column(
  children: [
    Collapsible(
      trigger: Text('기본 정보'),
      content: BasicInfoForm(),
      expanded: isBasicExpanded,
      onExpandedChanged: (v) => setState(() => isBasicExpanded = v),
    ),
    Collapsible(
      trigger: Text('추가 정보'),
      content: ExtraInfoForm(),
      expanded: isExtraExpanded,
      onExpandedChanged: (v) => setState(() => isExtraExpanded = v),
    ),
  ],
)

섹션들이 서로 독립적으로 열리고 닫혀 사용자가 원하는 섹션만 볼 수 있다.


❌ Don't#

너무 짧은 내용을 Collapsible로 감싸지 않기

// ❌ 한 줄 텍스트를 굳이 Collapsible로
Collapsible(
  trigger: Text('작성자'),
  content: Text('홍길동'),
)

짧은 내용은 바로 표시하는 것이 클릭 비용을 줄여 UX를 개선한다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Enter / Space트리거 토글 (펼침/접힘)
Tab다음 포커스 가능 요소로 이동

스크린 리더#

  • 양 플랫폼 모두 트리거를 Clickable 로 합성해 키보드 활성화·포커스 링을 받습니다
  • Flutter: Semantics(button: true, expanded: …)
  • Web: role="button", tabindex="0", aria-expanded

터치 타겟#

  • 트리거는 행 전체가 히트 영역이라 24×24 최소치(→ 전역 접근성 축)를 실질적으로 넘어선다. 실제 높이는 CoreSpace.space16 상하 패딩과 라벨 타이포그래피가 정하며 고정값이 아니다.

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

항목FlutterWeb
클래스명CollapsibleCollapsible
애니메이션 SizeTransition + AnimationController CSS grid-template-rows transition
ARIA Semantics(button:, expanded:) role="button", aria-expanded
화살표 아이콘 AnimatedBuilder + Transform.rotate CSS transform: rotate()
  • Accordion: 여러 Collapsible 패널을 그룹으로 관리할 때 사용
  • Tree: 계층적 데이터를 중첩 접기/펼치기로 표시할 때 사용