Chat | CoUI

Chat

메시지 목록과 입력창으로 구성된 채팅 UI 컴포넌트

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#

속성타입기본값설명
idString필수메시지 고유 식별자
contentString필수메시지 내용
senderChatSender필수발신자 구분 (me / other)
timestamp DateTime? null 메시지 발송 시각
avatar Widget? null 발신자 아바타 위젯

ChatBubble (Flutter) / ChatBubble (Web)#

속성타입기본값설명
messageString필수말풍선에 표시할 텍스트
senderChatSender필수발신자 구분
timestamp DateTime? null 메시지 시각
avatarWidget?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)#

항목FlutterWeb
클래스명 CouiChat / CouiChatBubble Chat / ChatBubble
전송 방식 onSend (Enter 키 또는 버튼) onSend (Enter 키 또는 버튼)
스크롤ListView 기반CSS 스크롤 컨테이너
이미지 지원avatar Widgetavatar URL 또는 Widget
  • Avatar: 메시지 발신자 아바타 표시에 사용
  • Loading: AI 응답 대기 시 isLoading 내부에서 사용
  • Input: 단순 텍스트 입력만 필요할 때 대안

조합 예제#

// 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,
)