Accordion#
콘텐츠를 접었다 펼 수 있는 아코디언 컴포넌트입니다.
Live Preview#
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) | 적용 영역 |
|---|---|---|
backgroundColor | Color? | 컨테이너 배경 색 |
borderColor | Color? | 외곽 보더 색 |
borderWidth | double? | 외곽 보더 두께 (px) |
borderRadius | double? | 외곽 모서리 반지름 (px) |
contentPaddingH | double? | 펼친 콘텐츠 좌/우 패딩 (px) |
contentPaddingV | double? | 펼친 콘텐츠 위/아래 패딩 (px) |
itemGap | double? | 항목 간 간격 (px) |
dividerColor | Color? | 항목 구분선 색 |
dividerThickness | double? | 항목 구분선 두께 (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#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title | String | 필수 | 헤더 제목 |
subtitle | String? | null | 부제목 |
leading | Widget? | null | 좌측 아이콘 |
child | Widget | 필수 | 펼침 내용 |
enabled | bool | true | 활성화 여부 |
변형 (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)#
| 항목 | Flutter | Web |
|---|---|---|
| 헤더 | Widget 기반 (AccordionTrigger) | String 기반 (title) |
| 애니메이션 | 테마로 커스터마이즈 (duration, curve) | Tailwind transition-all duration-200 |
| 다중 열기 | allowMultiple 속성 | allowMultiple 속성 |
| 아이콘 | AccordionTheme.arrowIcon 커스텀 | 고정 chevron |
| 구분선 | dividerHeight, dividerColor | CSS border |
| 테마 | AccordionTheme | Tailwind CSS |
관련 컴포넌트 (Related Components)#
조합 예제#
// 설정 페이지 패턴
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(),
),
],
)