EmptyState#
목록이나 콘텐츠 영역이 비어 있을 때 아이콘, 제목, 설명, 액션 버튼을 함께 표시하는 컴포넌트입니다.
Live Preview#
Web
No results found
Try adjusting your search or filters.
Flutter
Loading Flutter...
class EmptyStateDefaultExample extends StatefulComponent {
const EmptyStateDefaultExample({super.key});
@override
State<EmptyStateDefaultExample> createState() =>
_EmptyStateDefaultExampleState();
}
class _EmptyStateDefaultExampleState extends State<EmptyStateDefaultExample> {
@override
Component build(BuildContext context) {
return CoEmptyState(
title: text('No results found'),
description: text('Try adjusting your search or filters.'),
);
}
}
class EmptyStateDefaultExample extends StatefulWidget {
const EmptyStateDefaultExample({super.key});
@override
State<EmptyStateDefaultExample> createState() =>
_EmptyStateDefaultExampleState();
}
class _EmptyStateDefaultExampleState extends State<EmptyStateDefaultExample> {
@override
Widget build(BuildContext context) {
return const CoEmptyState(
title: Text('No results found'),
description: Text('Try adjusting your search or filters.'),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 목록이나 테이블에 표시할 데이터가 없을 때
- 검색 결과가 없는 경우 사용자에게 안내할 때
- 네트워크 오류로 데이터를 불러오지 못했을 때 재시도 옵션을 제공할 때
- 사용자가 아직 콘텐츠를 생성하지 않은 초기 상태에서 시작을 유도할 때
대신 다른 컴포넌트를 사용하세요:
Skeleton: 데이터 로딩 중인 상태를 표시할 때 (빈 상태가 아님)Loading: 단순히 작업 진행 중임을 표시할 때Banner: 페이지 전체 범위의 시스템 메시지가 필요할 때
기본 사용법 (Basic Usage)#
// 기본 빈 상태
CoEmptyState(
icon: Icon(Icons.inbox_outlined, size: 48),
title: '받은 메시지가 없습니다',
description: '새로운 메시지가 도착하면 여기에 표시됩니다.',
)
// 액션 버튼 포함
CoEmptyState(
icon: Icon(Icons.folder_open_outlined, size: 48),
title: '파일이 없습니다',
description: '파일을 업로드하거나 폴더를 만들어 시작하세요.',
action: Button.primary(
onPressed: handleUpload,
child: Text('파일 업로드'),
),
)
// 검색 결과 없음
CoEmptyState(
icon: Icon(Icons.search_off, size: 48),
title: '검색 결과가 없습니다',
description: '"Flutter"에 대한 결과를 찾을 수 없습니다.',
)
// 기본 빈 상태
CoEmptyState(
title: '받은 메시지가 없습니다',
description: '새로운 메시지가 도착하면 여기에 표시됩니다.',
)
// 액션 버튼 포함
CoEmptyState(
title: '파일이 없습니다',
description: '파일을 업로드하거나 폴더를 만들어 시작하세요.',
action: button(
[Component.text('파일 업로드')],
onClick: handleUpload,
classes: 'btn btn-primary',
),
)
// 검색 결과 없음
CoEmptyState(
title: '검색 결과가 없습니다',
description: '다른 검색어로 다시 시도해 보세요.',
action: button(
[Component.text('검색 초기화')],
onClick: handleResetSearch,
classes: 'btn btn-outline',
),
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title |
Widget / Component |
필수 | 빈 상태 제목 |
icon |
Widget? / Component? |
null |
상단에 표시할 아이콘 위젯 |
description |
Widget? (Flutter) / String? (Web) |
null |
부가 설명 텍스트 |
action |
Widget? / Component? |
null |
하단에 표시할 액션 버튼 |
compact |
bool |
false |
컴팩트 모드 사용 여부 |
padding |
EdgeInsetsGeometry? / double? |
null |
콘텐츠 패딩 |
iconSize |
double? |
null |
아이콘 컨테이너 크기 |
iconColor |
Color? / CoreColor? |
null |
아이콘 색상 |
minHeight | double? | null | 최소 높이 |
사용 시나리오#
목록이 비어 있는 경우#
if (items.isEmpty)
CoEmptyState(
icon: Icon(Icons.list_alt_outlined, size: 48),
title: '항목이 없습니다',
description: '새 항목을 추가하여 시작해 보세요.',
action: Button.primary(
onPressed: handleAdd,
child: Text('추가하기'),
),
)
검색 결과 없음#
CoEmptyState(
icon: Icon(Icons.search_off, size: 48),
title: '검색 결과가 없습니다',
description: '다른 검색어로 다시 시도해 보세요.',
)
오류 상태#
CoEmptyState(
icon: Icon(Icons.error_outline, size: 48),
title: '데이터를 불러오지 못했습니다',
description: '네트워크 연결을 확인하고 다시 시도해 주세요.',
action: Button.outline(
onPressed: handleRetry,
child: Text('다시 시도'),
),
)
동작 스펙 (Behavior)#
인터랙션#
- EmptyState 자체는 인터랙티브하지 않습니다.
action위젯에 전달된 버튼을 통해 사용자 액션을 제공합니다.
레이아웃#
- 아이콘, 제목, 설명, 액션 버튼이 수직으로 중앙 정렬
- 부모 위젯의 크기에 맞게 자동 확장 (
Expanded또는flex내에서 사용 권장)
애니메이션#
- 없음. 단, 빈 상태와 콘텐츠 사이 전환 시 부모에서
AnimatedSwitcher사용 권장
사용 가이드라인 (Usage Guidelines)#
✅ Do#
상황에 맞는 구체적인 메시지와 아이콘 사용
// 검색 결과 없음 - 구체적인 메시지
CoCoEmptyState(
icon: Icon(Icons.search_off, size: 48),
title: '"${searchQuery}"에 대한 결과가 없습니다',
description: '철자를 확인하거나 다른 검색어를 시도해 보세요.',
)
상황에 맞는 구체적인 메시지는 사용자가 다음에 무엇을 해야 하는지 명확히 이해하는 데 도움을 줍니다.
❌ Don't#
모든 빈 상태에 동일한 메시지 사용 금지
// ❌ 상황과 무관한 일반적인 메시지
CoCoEmptyState(
title: '데이터가 없습니다', // 너무 모호함
description: '나중에 다시 시도하세요.',
)
일반적인 메시지는 사용자에게 다음 단계를 안내하지 못합니다. 빈 이유와 해결 방법을 명확하게 전달하세요.
✅ Do#
액션 버튼으로 빈 상태 해소를 돕기
// 비어 있는 장바구니에서 쇼핑 유도
CoCoEmptyState(
icon: Icon(Icons.shopping_cart_outlined, size: 48),
title: '장바구니가 비어 있습니다',
description: '마음에 드는 상품을 담아보세요.',
action: CouiButton.primary(
onPressed: handleBrowseProducts,
child: Text('쇼핑 시작하기'),
),
)
빈 상태에서 다음 액션을 바로 제공하면 사용자 이탈을 줄일 수 있습니다.
❌ Don't#
로딩 중에 EmptyState 표시 금지
// ❌ 데이터 로딩 중에 빈 상태 표시
if (items.isEmpty) // isLoading 체크 없음
CoCoEmptyState(title: '항목이 없습니다')
로딩 중에 빈 상태가 먼저 표시되었다가 데이터가 나타나는 플래시(flash) 현상이 발생합니다. isLoading 상태를 먼저 체크하세요.
✅ Do#
빈 상태에서 사용자가 할 수 있는 액션을 제안하세요.
CoCoEmptyState(
icon: Icon(Icons.add_circle_outline),
title: '프로젝트가 없습니다',
description: '새 프로젝트를 만들어 시작하세요.',
action: CouiButton.primary(
onPressed: handleCreateProject,
child: Text('프로젝트 만들기'),
),
)
빈 상태는 사용자를 막힌 곳에 두는 것이 아니라 다음 단계로 안내하는 기회입니다. 명확한 CTA를 제공하세요.
❌ Don't#
단순히 '데이터 없음'만 표시하지 마세요.
// ❌ 설명도, 액션도 없는 빈 상태
CoCoEmptyState(
title: '데이터 없음',
)
맥락 없는 '데이터 없음' 메시지는 사용자를 혼란스럽게 합니다. 왜 비어있는지 이유와 해결 방법을 함께 제공하세요.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | action 버튼으로 이동 |
Enter / Space | 액션 버튼 활성화 |
스크린 리더#
- Flutter: 제목과 설명이 순서대로 읽히도록
Column구조 유지 - Web: 제목은
<h2>또는<h3>수준으로 렌더링되어 문서 구조 반영
터치 타겟#
action버튼은 최소 48x48dp
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CoEmptyState | CoEmptyState |
| 레이아웃 | Column + Center | Flexbox 수직 정렬 |
| 아이콘 | Flutter Icon 위젯 | SVG 또는 아이콘 폰트 |
관련 컴포넌트 (Related Components)#
조합 예제#
// 로딩 → 빈 상태 → 콘텐츠 전환 패턴
Widget buildContent() {
if (isLoading) {
return CouiSkeleton.text(lines: 5);
}
if (items.isEmpty) {
return CoCoEmptyState(
icon: Icon(Icons.inbox_outlined, size: 48),
title: '항목이 없습니다',
description: '새 항목을 추가하여 시작해 보세요.',
action: CouiButton.primary(
onPressed: handleAdd,
child: Text('추가하기'),
),
);
}
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(item: items[index]),
);
}