diff --git a/.gitignore b/.gitignore
index f8c6c2e..e97e825 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,7 @@ app-example
# generated native folders
/ios
/android
+
+# claude
+CLAUDE.md
+.claude/
\ No newline at end of file
diff --git a/src/entities/chat/index.ts b/src/entities/chat/index.ts
new file mode 100644
index 0000000..f6a888e
--- /dev/null
+++ b/src/entities/chat/index.ts
@@ -0,0 +1,2 @@
+export type { ChatRoomItemProps, Message, MessageItemProps } from "./model/types";
+export { ChatRoomItem, MessageBubble, MessageItem, MessageTime } from "./ui";
diff --git a/src/entities/chat/model/types.ts b/src/entities/chat/model/types.ts
new file mode 100644
index 0000000..dc322ae
--- /dev/null
+++ b/src/entities/chat/model/types.ts
@@ -0,0 +1,23 @@
+import type { ImageSource } from "expo-image";
+
+export interface ChatRoomItemProps {
+ profileImage: ImageSource;
+ roomName: string;
+ lastMessage: string;
+ unreadCount?: number;
+}
+
+export interface Message {
+ id: string;
+ text: string;
+ senderId: string;
+ /** "HH:mm" 포맷 */
+ sentAt: string;
+}
+
+export interface MessageItemProps {
+ message: Message;
+ isMine: boolean;
+ /** received 메시지일 때만 필요 */
+ profileImage?: ImageSource;
+}
diff --git a/src/entities/chat/ui/ChatRoomItem.tsx b/src/entities/chat/ui/ChatRoomItem.tsx
new file mode 100644
index 0000000..1abea3f
--- /dev/null
+++ b/src/entities/chat/ui/ChatRoomItem.tsx
@@ -0,0 +1,46 @@
+import { Text, View } from "react-native";
+
+import { ProfileAvatar } from "@/shared/ui";
+
+import type { ChatRoomItemProps } from "../model/types";
+
+export function ChatRoomItem({
+ profileImage,
+ roomName,
+ lastMessage,
+ unreadCount = 0,
+}: ChatRoomItemProps) {
+ return (
+
+ {/* Left: profile + texts */}
+
+
+
+ {/* Text area */}
+
+
+ {roomName}
+
+
+ {lastMessage}
+
+
+
+
+ {/* Right: unread count badge */}
+ {unreadCount > 0 && (
+
+
+ {unreadCount}
+
+
+ )}
+
+ );
+}
diff --git a/src/entities/chat/ui/MessageBubble.tsx b/src/entities/chat/ui/MessageBubble.tsx
new file mode 100644
index 0000000..c5b8470
--- /dev/null
+++ b/src/entities/chat/ui/MessageBubble.tsx
@@ -0,0 +1,28 @@
+import { Text, View } from "react-native";
+
+interface MessageBubbleProps {
+ text: string;
+ variant: "sent" | "received";
+}
+
+export function MessageBubble({ text, variant }: MessageBubbleProps) {
+ const isSent = variant === "sent";
+
+ return (
+
+
+ {text}
+
+
+ );
+}
diff --git a/src/entities/chat/ui/MessageItem.tsx b/src/entities/chat/ui/MessageItem.tsx
new file mode 100644
index 0000000..06272c6
--- /dev/null
+++ b/src/entities/chat/ui/MessageItem.tsx
@@ -0,0 +1,36 @@
+import { View } from "react-native";
+
+import { ProfileAvatar } from "@/shared/ui";
+
+import type { MessageItemProps } from "../model/types";
+import { MessageBubble } from "./MessageBubble";
+import { MessageTime } from "./MessageTime";
+
+export function MessageItem({ message, isMine, profileImage }: MessageItemProps) {
+ const variant = isMine ? "sent" : "received";
+
+ if (isMine) {
+ return (
+
+
+ {/* shrink: 버블이 시간 텍스트를 밀어내지 않도록 압축 허용 */}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* flex-1: 아바타 이후 남은 공간을 정확히 파악해 버블+시간이 넘치지 않게 함 */}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/entities/chat/ui/MessageTime.tsx b/src/entities/chat/ui/MessageTime.tsx
new file mode 100644
index 0000000..e22b57d
--- /dev/null
+++ b/src/entities/chat/ui/MessageTime.tsx
@@ -0,0 +1,19 @@
+import { Text } from "react-native";
+
+interface MessageTimeProps {
+ time: string;
+ variant: "sent" | "received";
+}
+
+export function MessageTime({ time, variant }: MessageTimeProps) {
+ return (
+
+ {time}
+
+ );
+}
diff --git a/src/entities/chat/ui/README.md b/src/entities/chat/ui/README.md
new file mode 100644
index 0000000..9e52037
--- /dev/null
+++ b/src/entities/chat/ui/README.md
@@ -0,0 +1,150 @@
+# entities/chat/ui
+
+채팅 도메인 UI 컴포넌트 모음.
+
+---
+
+## ChatRoomItem
+
+채팅방 목록에서 각 행을 표시하는 컴포넌트.
+
+```tsx
+import { ChatRoomItem } from "@/entities/chat";
+
+
+```
+
+| Prop | 타입 | 필수 | 설명 |
+|------|------|------|------|
+| `profileImage` | `ImageSource` | ✅ | 프로필 이미지 |
+| `roomName` | `string` | ✅ | 채팅방 이름 |
+| `lastMessage` | `string` | ✅ | 마지막 메시지 |
+| `unreadCount` | `number` | ❌ | 읽지 않은 메시지 수. 0이면 배지 미표시. |
+
+---
+
+## MessageItem
+
+받은 메시지·보낸 메시지를 모두 처리하는 조합 컴포넌트. `isMine` 값에 따라 레이아웃이 달라진다.
+
+```tsx
+import { MessageItem } from "@/entities/chat";
+
+// 받은 메시지
+
+
+// 보낸 메시지
+
+```
+
+| Prop | 타입 | 필수 | 설명 |
+|------|------|------|------|
+| `message` | `Message` | ✅ | 메시지 데이터 |
+| `isMine` | `boolean` | ✅ | `true`면 오른쪽 정렬(파란 버블), `false`면 왼쪽 정렬(흰 버블) |
+| `profileImage` | `ImageSource` | ❌ | 받은 메시지(`isMine=false`)일 때만 사용 |
+
+### Message 타입
+
+```ts
+interface Message {
+ id: string;
+ text: string;
+ senderId: string;
+ sentAt: string; // "HH:mm" 포맷
+ unreadCount?: number; // 프로필 우하단 배지 숫자
+}
+```
+
+---
+
+## MessageBubble
+
+말풍선 단독 컴포넌트. `MessageItem` 내부에서 사용되며, 필요 시 단독으로도 사용 가능.
+
+```tsx
+import { MessageBubble } from "@/entities/chat";
+
+
+
+```
+
+| Prop | 타입 | 설명 |
+|------|------|------|
+| `text` | `string` | 메시지 텍스트 |
+| `variant` | `"sent" \| "received"` | `sent`=파란 배경, `received`=neutral 배경 |
+
+---
+
+## MessageTime
+
+전송 시간 텍스트 단독 컴포넌트.
+
+```tsx
+import { MessageTime } from "@/entities/chat";
+
+
+
+```
+
+| Prop | 타입 | 설명 |
+|------|------|------|
+| `time` | `string` | `"HH:mm"` 포맷 문자열 |
+| `variant` | `"sent" \| "received"` | 정렬 방향 결정 |
+
+---
+
+## 채팅방 화면에서의 레이아웃
+
+```tsx
+import { FlatList, KeyboardAvoidingView, Platform } from "react-native";
+import { MessageItem } from "@/entities/chat";
+import { ChatBar } from "@/features/send-message";
+
+function ChatRoomPage() {
+ return (
+
+ item.id}
+ renderItem={({ item }) => (
+
+ )}
+ contentContainerStyle={{ gap: 12, paddingVertical: 16 }}
+ inverted
+ />
+
+
+
+ );
+}
+```
diff --git a/src/entities/chat/ui/index.ts b/src/entities/chat/ui/index.ts
new file mode 100644
index 0000000..bc3fe9b
--- /dev/null
+++ b/src/entities/chat/ui/index.ts
@@ -0,0 +1,4 @@
+export { ChatRoomItem } from "./ChatRoomItem";
+export { MessageBubble } from "./MessageBubble";
+export { MessageItem } from "./MessageItem";
+export { MessageTime } from "./MessageTime";
diff --git a/src/features/send-message/index.ts b/src/features/send-message/index.ts
new file mode 100644
index 0000000..0aec626
--- /dev/null
+++ b/src/features/send-message/index.ts
@@ -0,0 +1 @@
+export { ChatBar } from "./ui";
diff --git a/src/features/send-message/ui/ChatBar.tsx b/src/features/send-message/ui/ChatBar.tsx
new file mode 100644
index 0000000..1100752
--- /dev/null
+++ b/src/features/send-message/ui/ChatBar.tsx
@@ -0,0 +1,59 @@
+import { Ionicons } from "@expo/vector-icons";
+import { useState } from "react";
+import { TextInput, TouchableOpacity, View } from "react-native";
+
+import { colorTokens } from "@/shared/styles/tokens";
+
+interface ChatBarProps {
+ onSend: (message: string) => void;
+ onAttach?: () => void;
+}
+
+export function ChatBar({
+ onSend,
+ onAttach,
+}: ChatBarProps) {
+ const [text, setText] = useState("");
+
+ const canSend = text.trim().length > 0;
+
+ function handleSend() {
+ if (!canSend) return;
+ onSend(text.trim());
+ setText("");
+ }
+
+ return (
+
+
+ {/* 첨부(+) 버튼 */}
+
+
+
+
+ {/* 텍스트 입력 */}
+
+
+ {/* 전송 버튼 */}
+
+
+
+
+
+ );
+}
diff --git a/src/features/send-message/ui/README.md b/src/features/send-message/ui/README.md
new file mode 100644
index 0000000..f88436c
--- /dev/null
+++ b/src/features/send-message/ui/README.md
@@ -0,0 +1,55 @@
+# ChatBar
+
+메시지 전송을 위한 채팅 입력 바 컴포넌트입니다. 텍스트가 길어지면 입력창이 위로 자동 확장됩니다.
+
+## 사용법
+
+```tsx
+import { ChatBar } from "@/features/send-message";
+
+ console.log(message)}
+ onAttach={() => console.log("attach pressed")}
+/>
+```
+
+## Props
+
+| Prop | 타입 | 필수 여부 | 설명 |
+|------|------|----------|------|
+| `onSend` | `(message: string) => void` | ✅ | 전송 버튼 클릭 시 호출. 앞뒤 공백이 제거된 텍스트가 전달되며, 전송 후 입력창은 초기화됩니다. |
+| `onAttach` | `() => void` | ❌ | `+` 버튼 클릭 시 호출. |
+
+## 동작 방식
+
+- 입력창이 비어있으면 전송 버튼이 **비활성화**(회색), 텍스트가 있으면 **활성화**(파란색)됩니다.
+- 텍스트가 한 줄을 넘으면 입력창이 위로 확장되며, 아이콘은 하단에 고정됩니다.
+- `onSend`에는 앞뒤 공백이 제거된 텍스트가 전달됩니다.
+- Android 기본 `TextInput` 패딩을 제거하여 플랫폼 간 정렬이 일관되게 유지됩니다.
+
+## 채팅 화면에서의 레이아웃
+
+```tsx
+import { KeyboardAvoidingView, Platform } from "react-native";
+import { ChatBar } from "@/features/send-message";
+
+function ChatRoomPage() {
+ function handleSend(message: string) {
+ // 서버에 메시지 전송
+ }
+
+ return (
+
+ {/* 메시지 목록 */}
+
+
+
+
+ );
+}
+```
+
+> **주의:** 키보드가 올라올 때 `ChatBar`가 함께 올라오도록 `KeyboardAvoidingView`로 화면을 감싸야 합니다.
diff --git a/src/features/send-message/ui/index.ts b/src/features/send-message/ui/index.ts
new file mode 100644
index 0000000..724376a
--- /dev/null
+++ b/src/features/send-message/ui/index.ts
@@ -0,0 +1 @@
+export { ChatBar } from "./ChatBar";
diff --git a/src/shared/assets/fonts/Pretendard-ExtraLight.otf b/src/shared/assets/fonts/Pretendard-ExtraLight.otf
new file mode 100644
index 0000000..40c8b69
Binary files /dev/null and b/src/shared/assets/fonts/Pretendard-ExtraLight.otf differ
diff --git a/src/shared/assets/fonts/Pretendard-Light.otf b/src/shared/assets/fonts/Pretendard-Light.otf
new file mode 100644
index 0000000..228679e
Binary files /dev/null and b/src/shared/assets/fonts/Pretendard-Light.otf differ
diff --git a/src/shared/assets/images/default-profile.png b/src/shared/assets/images/default-profile.png
new file mode 100644
index 0000000..755ec18
Binary files /dev/null and b/src/shared/assets/images/default-profile.png differ
diff --git a/src/shared/lib/hooks/useLoadFonts.ts b/src/shared/lib/hooks/useLoadFonts.ts
index f4e89e8..b779ff8 100644
--- a/src/shared/lib/hooks/useLoadFonts.ts
+++ b/src/shared/lib/hooks/useLoadFonts.ts
@@ -7,6 +7,8 @@ SplashScreen.preventAutoHideAsync();
export function useLoadFonts() {
const [fontsLoaded] = useFonts({
+ "Pretendard-ExtraLight": require("@/shared/assets/fonts/Pretendard-ExtraLight.otf"),
+ "Pretendard-Light": require("@/shared/assets/fonts/Pretendard-Light.otf"),
"Pretendard-Regular": require("@/shared/assets/fonts/Pretendard-Regular.otf"),
"Pretendard-Medium": require("@/shared/assets/fonts/Pretendard-Medium.otf"),
"Pretendard-SemiBold": require("@/shared/assets/fonts/Pretendard-SemiBold.otf"),
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
index e4c0223..ac09c9b 100644
--- a/src/shared/ui/index.ts
+++ b/src/shared/ui/index.ts
@@ -1,2 +1,2 @@
+export * from "./profile";
export * from "./select";
-
diff --git a/src/shared/ui/profile/ProfileAvatar.tsx b/src/shared/ui/profile/ProfileAvatar.tsx
new file mode 100644
index 0000000..c0f81f4
--- /dev/null
+++ b/src/shared/ui/profile/ProfileAvatar.tsx
@@ -0,0 +1,28 @@
+import { Image } from "expo-image";
+import type { ImageSource } from "expo-image";
+import { View } from "react-native";
+
+const DEFAULT_PROFILE = require("@/shared/assets/images/default-profile.png");
+
+interface ProfileAvatarProps {
+ source?: ImageSource;
+ /** 프레임 크기 (px). 이미지는 size-2 만큼 작게 렌더링. default: 48 */
+ size?: number;
+}
+
+export function ProfileAvatar({ source, size = 48 }: ProfileAvatarProps) {
+ const imageSize = size - 2;
+
+ return (
+
+
+
+ );
+}
diff --git a/src/shared/ui/profile/index.ts b/src/shared/ui/profile/index.ts
new file mode 100644
index 0000000..fa4c323
--- /dev/null
+++ b/src/shared/ui/profile/index.ts
@@ -0,0 +1 @@
+export { ProfileAvatar } from "./ProfileAvatar";
diff --git a/tailwind.config.js b/tailwind.config.js
index e643e37..167541e 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -46,6 +46,8 @@ module.exports = {
plugins: [
({ addUtilities }) => {
addUtilities({
+ ".font-extralight": { fontFamily: "Pretendard-ExtraLight" },
+ ".font-light": { fontFamily: "Pretendard-Light" },
".font-regular": { fontFamily: "Pretendard-Regular" },
".font-medium": { fontFamily: "Pretendard-Medium" },
".font-semibold": { fontFamily: "Pretendard-SemiBold" },