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