Accordion | CoUI

Accordion

아코디언/접기 컴포넌트

Accordion#

콘텐츠를 접었다 펼 수 있는 아코디언 컴포넌트입니다.

Live Preview#

Web
Is it accessible?
Yes. It adheres to the WAI-ARIA design pattern.
Yes. It comes with default styles that matches the other components.
Yes. It's animated by default, but you can disable it if you prefer.
Flutter
Loading Flutter...
class AccordionDefaultExample extends StatelessComponent {
  const AccordionDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return CoAccordion(
      items: [
        CoAccordionItem(
          title: 'Is it accessible?',
          content: text('Yes. It adheres to the WAI-ARIA design pattern.'),
          expanded: true,
        ),
        CoAccordionItem(
          title: 'Is it styled?',
          content: text('Yes. It comes with default styles that matches the other components.'),
        ),
        CoAccordionItem(
          title: 'Is it animated?',
          content: text('Yes. It\'s animated by default, but you can disable it if you prefer.'),
        ),
      ],
    );
  }
}
class AccordionDefaultExample extends StatelessWidget {
  const AccordionDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const CoAccordion(
      items: [
        CoAccordionItem(
          title: 'Is it accessible?',
          content: Text('Yes. It adheres to the WAI-ARIA design pattern.'),
          expanded: true,
        ),
        CoAccordionItem(
          title: 'Is it styled?',
          content: Text(
            'Yes. It comes with default styles that matches the other components.',
          ),
        ),
        CoAccordionItem(
          title: 'Is it animated?',
          content: Text(
            "Yes. It's animated by default, but you can disable it if you prefer.",
          ),
        ),
      ],
    );
  }
}

사용 시기 (When to Use)#

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

  • FAQ, 도움말 등 질문-답변 형태의 콘텐츠를 표시할 때
  • 긴 내용을 섹션별로 접어서 공간을 절약할 때
  • 설정 패널의 카테고리별 옵션을 그룹화할 때

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

  • Tabs: 관련 콘텐츠를 탭으로 전환할 때 (한 번에 하나만 보여도 될 때)
  • Dialog: 추가 정보를 모달로 보여줄 때
  • Drawer: 복잡한 콘텐츠를 사이드 패널로 보여줄 때

기본 사용법 (Basic Usage)#

// 기본 아코디언
Accordion(
  items: [
    AccordionItem(
      title: '섹션 1',
      child: Text('섹션 1의 내용입니다.'),
    ),
    AccordionItem(
      title: '섹션 2',
      child: Text('섹션 2의 내용입니다.'),
    ),
    AccordionItem(
      title: '섹션 3',
      child: Text('섹션 3의 내용입니다.'),
    ),
  ],
)

// 다중 열기 허용
Accordion(
  allowMultiple: true,
  items: accordionItems,
)

// 초기 열림 상태
Accordion(
  initialOpenIndexes: [0],
  items: accordionItems,
)
// 기본 아코디언
Accordion(
  children: [
    AccordionItem(
      title: '섹션 1',
      content: Component.text('섹션 1의 내용입니다.'),
    ),
    AccordionItem(
      title: '섹션 2',
      content: Component.text('섹션 2의 내용입니다.'),
    ),
    AccordionItem(
      title: '섹션 3',
      content: Component.text('섹션 3의 내용입니다.'),
    ),
  ],
)

// 다중 열기 허용 (각 항목을 개별적으로 isOpen으로 제어)
Accordion(
  children: [
    AccordionItem(
      title: 'FAQ 1',
      content: Component.text('FAQ 1 답변입니다.'),
      isOpen: true,
    ),
    AccordionItem(
      title: 'FAQ 2',
      content: Component.text('FAQ 2 답변입니다.'),
      isOpen: true,
    ),
  ],
)

// 초기 열림 상태
Accordion(
  children: [
    AccordionItem(
      title: '기본 정보',
      content: Component.text('기본 정보 내용입니다.'),
      isOpen: true, // 초기 열림 상태
    ),
    AccordionItem(
      title: '추가 정보',
      content: Component.text('추가 정보 내용입니다.'),
    ),
  ],
)

Props / Parameters#

Accordion#

속성타입기본값설명
items List<AccordionItem> 필수 아코디언 항목 목록
allowMultiple bool false 동시에 여러 항목 열기 허용
initialOpenIndexes List<int> [] 초기 열린 항목 인덱스
onChanged ValueChanged<List<int>>? null 상태 변경 콜백
accordionStyle CoreAccordionStyle<Color>? / CoreAccordionStyle<CoreColor>? null 인스턴스 스타일 (Style 시스템 참조)

스타일 시스템 (Style System)#

Accordion 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreAccordionStyle<Clr> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 시맨틱 enum / behaviour (items / allowMultiple) 는 위젯 파라미터로 직접 전달합니다.

시맨틱 vs 스타일#

  • 시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (items, allowMultiple)
  • chrome / dimensional / 슬롯 스타일: CoreAccordionStyle 한 곳으로 (backgroundColor / borderColor / borderWidth / borderRadius / contentPaddingH / contentPaddingV / itemGap / dividerColor / dividerThickness / triggerStyle / contentTextStyle / chevronIconStyle)

Resolve chain#

design system default for accordion
  → CoreAccordionTheme.style                   // 프로젝트 공통
  → parent component slot override
  → widget.accordionStyle                      // 인스턴스별

각 nested 슬롯 스타일 (triggerStyle / contentTextStyle / chevronIconStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.

슬롯 매핑 (CoreAccordionStyle 12 필드)#

필드타입 (Flutter)적용 영역
backgroundColorColor?컨테이너 배경 색
borderColorColor?외곽 보더 색
borderWidthdouble?외곽 보더 두께 (px)
borderRadiusdouble?외곽 모서리 반지름 (px)
contentPaddingHdouble?펼친 콘텐츠 좌/우 패딩 (px)
contentPaddingVdouble?펼친 콘텐츠 위/아래 패딩 (px)
itemGapdouble?항목 간 간격 (px)
dividerColorColor?항목 구분선 색
dividerThicknessdouble?항목 구분선 두께 (px)
triggerStyle CoreButtonStyle<Color>? 트리거 chrome (paddingH / paddingV / labelStyle 활용 가능)
contentTextStyle CoreTextStyle<Color>? 펼친 콘텐츠 기본 텍스트 스타일
chevronIconStyle CoreIconStyle<Color>? 회전 chevron 아이콘 스타일

Migration — 옛 평면 chrome → 새 위치 매핑#

기존 CoreAccordionTheme 의 평면 필드 (padding / iconGap / dividerHeight / dividerColor / arrowIconColor) 는 호환을 위해 유지되지만, 새 코드는 accordionStyle 슬롯을 사용하세요:

기존 (legacy theme 필드)새 위치
CoreAccordionTheme.padding accordionStyle.contentPaddingV 또는 triggerStyle.paddingV
CoreAccordionTheme.dividerHeight accordionStyle.dividerThickness
CoreAccordionTheme.dividerColor accordionStyle.dividerColor
CoreAccordionTheme.arrowIconColor accordionStyle.chevronIconStyle.color
인스턴스 트리거 라벨 스타일 (불가능)accordionStyle.triggerStyle.labelStyle
인스턴스 콘텐츠 텍스트 스타일 (불가능)accordionStyle.contentTextStyle
인스턴스 chevron 사이즈 (불가능)accordionStyle.chevronIconStyle.size

사용 예 (Flutter)#

CoAccordion(
  items: [
    CoAccordionItem(
      title: 'FAQ Question',
      content: Text('Answer'),
    ),
  ],
  accordionStyle: CoreAccordionStyle(
    contentPaddingV: 24,
    dividerColor: Color(0xFFE5E7EB),
    dividerThickness: 1,
    triggerStyle: CoreButtonStyle(
      paddingV: 16,
      labelStyle: CoreTextStyle(fontSize: 16, fontWeight: 600),
    ),
    contentTextStyle: CoreTextStyle(fontSize: 14),
    chevronIconStyle: CoreIconStyle(size: 20),
  ),
)

사용 예 (Web)#

CoAccordion(
  items: [
    CoAccordionItem(
      title: 'FAQ Question',
      content: Component.text('Answer'),
    ),
  ],
  accordionStyle: CoreAccordionStyle<CoreColor>(
    contentPaddingV: 24,
    dividerThickness: 1,
    triggerStyle: CoreButtonStyle<CoreColor>(
      paddingV: 16,
    ),
  ),
)

AccordionItem#

속성타입기본값설명
titleString필수헤더 제목
subtitleString?null부제목
leadingWidget?null좌측 아이콘
childWidget필수펼침 내용
enabledbooltrue활성화 여부

변형 (Variants)#

아이콘 포함#

AccordionItem(
  leading: Icon(Icons.person),
  title: '개인 정보',
  child: PersonalInfoForm(),
)

FAQ 스타일#

Accordion(
  items: faqList.map((faq) => AccordionItem(
    title: faq.question,
    child: Text(faq.answer),
  )).toList(),
)

단일 항목 (접기/펼치기)#

AccordionItem(
  title: '고급 설정',
  initiallyOpen: false,
  child: AdvancedSettingsPanel(),
)

동작 스펙 (Behavior)#

열기/닫기#

  • 헤더 클릭 시 해당 섹션이 슬라이드 애니메이션으로 펼쳐짐/접힘
  • allowMultiple: false (기본): 하나를 열면 이전에 열린 항목이 자동으로 닫힘
  • allowMultiple: true: 여러 항목을 동시에 열 수 있음

애니메이션#

  • 기본 200ms, easeIn/easeOut 커브
  • AccordionTheme으로 duration, curve, reverseCurve 커스터마이즈 가능
AccordionTheme(
  duration: Duration(milliseconds: 300),
  curve: Curves.easeInOut,
)

화살표 아이콘#

  • 헤더 우측에 화살표 아이콘이 열림/닫힘 방향을 표시
  • Flutter: AccordionTheme.arrowIcon으로 커스텀 가능
  • Web: 고정 chevron 아이콘 (180° 회전 트랜지션)

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

✅ Do#

자주 묻는 질문에 아코디언을 활용하세요.

Accordion(
  items: faqList.map((faq) => AccordionItem(
    title: faq.question,
    child: Text(faq.answer),
  )).toList(),
)

질문을 훑어보고 원하는 답변만 펼쳐 볼 수 있습니다.


❌ Don't#

필수로 봐야 하는 내용을 아코디언에 숨기지 마세요.

Accordion(items: [
  AccordionItem(
    title: '결제 정보',
    child: PaymentForm(), // 반드시 입력해야 하는 폼
  ),
])

중요한 내용이 접혀 있으면 사용자가 놓칠 수 있습니다.

✅ Do#

섹션 수가 많으면 아코디언을 사용하세요.

Accordion(
  items: categories.map((cat) => AccordionItem(
    leading: Icon(cat.icon),
    title: cat.name,
    child: cat.settingsPanel,
  )).toList(),
)

설정 페이지처럼 많은 섹션을 한 페이지에 담을 수 있습니다.


❌ Don't#

항목이 2개 이하이면 아코디언을 사용하지 마세요.

Accordion(items: [
  AccordionItem(title: '유일한 섹션', child: content),
])

항목이 적으면 접기/펼치기가 불필요한 단계입니다. 그냥 보여주세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Enter / Space포커스된 헤더 열기/닫기
Tab다음 아코디언 헤더로 이동
Shift+Tab이전 아코디언 헤더로 이동

스크린 리더#

  • Flutter: 헤더에 "펼침/접힘" 상태가 Semantics로 전달
  • Web: <button> 요소로 렌더링되어 네이티브 접근성 자동 적용

터치 타겟#

  • 헤더 최소 높이: 48dp

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

항목FlutterWeb
헤더Widget 기반 (AccordionTrigger)String 기반 (title)
애니메이션 테마로 커스터마이즈 (duration, curve) Tailwind transition-all duration-200
다중 열기allowMultiple 속성allowMultiple 속성
아이콘AccordionTheme.arrowIcon 커스텀고정 chevron
구분선dividerHeight, dividerColorCSS border
테마AccordionThemeTailwind CSS
  • Tabs: 콘텐츠를 탭으로 전환. 아코디언과 달리 한 번에 하나의 패널만 표시
  • Card: 콘텐츠 컨테이너. 아코디언 항목 내부에 카드를 배치 가능

조합 예제#

// 설정 페이지 패턴
Accordion(
  allowMultiple: true,
  items: [
    AccordionItem(
      leading: Icon(Icons.person),
      title: '프로필 설정',
      child: ProfileSettingsForm(),
    ),
    AccordionItem(
      leading: Icon(Icons.notifications),
      title: '알림 설정',
      child: NotificationSettingsForm(),
    ),
    AccordionItem(
      leading: Icon(Icons.security),
      title: '보안 설정',
      child: SecuritySettingsForm(),
    ),
  ],
)