Chat#
메시지 목록과 입력창을 포함한 채팅 인터페이스 컴포넌트입니다. ChatBubble, ChatInput 하위 컴포넌트로 구성됩니다.
Live Preview#
Web
Support Chat
Hello! How can I help?
I have a question.
Flutter
Loading Flutter...
Chat([
ChatHeader([text('Support Chat')]),
ChatBubble([text('Hello!')],
style: [ChatBubble.primary]),
ChatBubble([text('Hi there!')]),
], style: [Chat.start])
// Chat — conceptual card layout
Card(
child: Column(
children: [
Text('Support Chat'),
Text('Hello! How can I help?'),
Text('I have a question.'),
],
),
)
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 사용자 간 또는 사용자와 AI/봇 간의 실시간 메시지 교환 UI가 필요할 때
- 고객 지원 채팅 위젯이나 인앱 메신저를 구현할 때
- AI 어시스턴트와의 대화형 인터페이스를 구현할 때
대신 다른 컴포넌트를 사용하세요:
Timeline: 시간순 이벤트 기록을 표시할 때 (채팅이 아닌 활동 로그)Input+Button: 단순한 한 줄 텍스트 입력만 필요할 때
기본 사용법 (Basic Usage)#
// 기본 채팅 UI
Chat(
messages: [
ChatMessage(
id: '1',
content: '안녕하세요!',
sender: ChatSender.other,
timestamp: DateTime.now(),
),
ChatMessage(
id: '2',
content: '반갑습니다.',
sender: ChatSender.me,
timestamp: DateTime.now(),
),
],
onSend: handleSendMessage,
placeholder: '메시지를 입력하세요...',
)
// 로딩 상태
Chat(
messages: messages,
onSend: handleSendMessage,
isLoading: true,
)
// 개별 버블 사용
ChatBubble(
message: '안녕하세요!',
sender: ChatSender.other,
timestamp: DateTime.now(),
)
// 기본 채팅 (start = 왼쪽, end = 오른쪽 정렬)
Chat(
[
ChatImage([Avatar(src: '/avatar.png')]),
ChatHeader([Component.text('상대방')]),
ChatBubble([Component.text('안녕하세요!')]),
ChatFooter([Component.text('오전 10:00')]),
],
style: [Chat.start],
)
// 내 메시지 (오른쪽 정렬)
Chat(
[
ChatBubble(
[Component.text('반갑습니다.')],
style: [ChatBubble.primary],
),
ChatFooter([Component.text('오전 10:01')]),
],
style: [Chat.end],
)
// 로딩 상태 (응답 대기 중)
Chat(
[
ChatImage([Avatar(src: '/bot.png')]),
ChatBubble([
div([], classes: 'loading loading-dots loading-sm'),
]),
],
style: [Chat.start],
)
Props / Parameters#
Chat (Flutter) / Chat (Web)#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
messages |
List<ChatMessage> |
필수 | 표시할 메시지 목록 |
onSend |
void Function(String text) |
필수 | 메시지 전송 핸들러 |
placeholder |
String |
'메시지 입력...' |
입력 필드 플레이스홀더 |
isLoading |
bool |
false |
응답 대기 중 로딩 표시 |
maxInputLines |
int |
4 |
입력창 최대 줄 수 |
ChatMessage#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
id | String | 필수 | 메시지 고유 식별자 |
content | String | 필수 | 메시지 내용 |
sender | ChatSender | 필수 | 발신자 구분 (me / other) |
timestamp |
DateTime? |
null |
메시지 발송 시각 |
avatar |
Widget? |
null |
발신자 아바타 위젯 |
ChatBubble (Flutter) / ChatBubble (Web)#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
message | String | 필수 | 말풍선에 표시할 텍스트 |
sender | ChatSender | 필수 | 발신자 구분 |
timestamp |
DateTime? |
null |
메시지 시각 |
avatar | Widget? | null | 아바타 위젯 |
하위 컴포넌트 (Sub-components)#
ChatBubble#
개별 메시지를 말풍선 형태로 표시합니다. 발신자에 따라 좌우 정렬이 달라집니다.
ChatInput#
텍스트 입력 필드와 전송 버튼으로 구성된 입력 영역입니다.
동작 스펙 (Behavior)#
인터랙션#
- 메시지 전송: 입력 후 전송 버튼 클릭 또는
Enter키로onSend콜백 호출 - 줄바꿈:
Shift+Enter로 멀티라인 입력 지원 (Flutter:maxInputLines내에서) - 자동 스크롤: 새 메시지 추가 시 자동으로 최하단으로 스크롤
상태 전환#
- 입력 중: 전송 버튼 활성화
- 빈 입력: 전송 버튼 비활성화
isLoading: true: 입력창 비활성화 + 응답 대기 로딩 인디케이터 표시
애니메이션#
- 새 메시지 등장: 페이드인 + 아래에서 위로 슬라이드 150ms
- 로딩 인디케이터: 세 점이 순서대로 bounce 하는 dots 애니메이션
사용 가이드라인 (Usage Guidelines)#
✅ Do#
메시지에 타임스탬프와 아바타 제공
ChatMessage(
id: message.id,
content: message.text,
sender: message.isMe ? ChatSender.me : ChatSender.other,
timestamp: message.createdAt,
avatar: CouiAvatar(src: message.senderAvatar),
)
타임스탬프와 아바타는 대화의 맥락을 이해하는 데 도움을 주며, 여러 참여자가 있는 채팅에서 특히 중요합니다.
❌ Don't#
메시지 목록 변경 시 전체 재렌더링 방지
// ❌ 전체 messages 리스트를 교체
setState(() {
messages = [...messages, newMessage]; // 매번 새 리스트 생성
});
많은 메시지가 있을 때 전체 재렌더링이 발생하면 성능이 저하됩니다. 적절한 리스트 키를 사용하고 ListView.builder로 가상화를 활용하세요.
✅ Do#
AI/봇 응답 대기 중에는 isLoading 사용
CouiChat(
messages: messages,
onSend: (text) async {
setState(() => isLoading = true);
final response = await aiService.sendMessage(text);
setState(() {
messages = [...messages, response];
isLoading = false;
});
},
isLoading: isLoading,
)
로딩 상태를 표시하면 사용자가 응답을 기다리고 있음을 알 수 있어 UX가 개선됩니다.
❌ Don't#
채팅 컴포넌트에 과도한 커스텀 스타일 직접 적용 금지
// ❌ 개별 ChatBubble 스타일 무분별 커스텀
CouiChatBubble(
message: '...',
sender: ChatSender.me,
// 직접 색상, 패딩 등을 하드코딩
)
테마 시스템을 활용하면 다크 모드, 접근성 등을 일관되게 지원할 수 있습니다.
✅ Do#
메시지 전송 시 즉각적인 로딩 피드백을 제공하세요.
CouiChatMessage(
content: '잠시 후 답변 드리겠습니다...',
isLoading: true, // 응답 대기 중 스켈레톤 표시
role: ChatRole.assistant,
)
응답 지연 시 로딩 상태를 시각적으로 보여주면 사용자가 시스템이 정상 작동 중임을 인식할 수 있습니다.
❌ Don't#
사용자 메시지와 시스템 메시지를 구분 없이 표시하지 마세요.
// ❌ role 없이 모든 메시지를 동일하게 표시
CouiChatMessage(content: '안녕하세요!')
CouiChatMessage(content: '무엇을 도와드릴까요?') // 발신자 구분 불가
role(user/assistant/system)을 반드시 지정해야 시각적 구분이 가능하며 대화 흐름을 명확히 전달할 수 있습니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Enter | 메시지 전송 |
Shift+Enter | 줄바꿈 (멀티라인) |
Tab | 입력창과 전송 버튼 간 이동 |
스크린 리더#
- Flutter: 새 메시지 수신 시
Semantics로 발신자와 내용 알림 - Web:
role="log",aria-live="polite"적용으로 새 메시지 자동 알림
터치 타겟#
- 전송 버튼 최소 크기: 48x48dp
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CouiChat / CouiChatBubble |
Chat / ChatBubble |
| 전송 방식 | onSend (Enter 키 또는 버튼) |
onSend (Enter 키 또는 버튼) |
| 스크롤 | ListView 기반 | CSS 스크롤 컨테이너 |
| 이미지 지원 | avatar Widget | avatar URL 또는 Widget |
관련 컴포넌트 (Related Components)#
조합 예제#
// Chat + Avatar 조합으로 그룹 채팅 구현
CouiChat(
messages: messages.map((msg) => ChatMessage(
id: msg.id,
content: msg.content,
sender: msg.userId == currentUserId ? ChatSender.me : ChatSender.other,
avatar: CouiAvatar(
src: msg.senderAvatar,
fallback: msg.senderName[0],
),
timestamp: msg.createdAt,
)).toList(),
onSend: handleSendMessage,
isLoading: isWaitingResponse,
)