diff --git a/src/components/todo-card/TodoCard.stories.tsx b/src/components/todo-card/TodoCard.stories.tsx
new file mode 100644
index 0000000..f051a8e
--- /dev/null
+++ b/src/components/todo-card/TodoCard.stories.tsx
@@ -0,0 +1,116 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import type { ComponentProps } from 'react';
+
+import { useState } from 'react';
+import { fn } from 'storybook/test';
+
+import TodoCard from './TodoCard';
+import type { TodoItem } from './types/types';
+
+const sampleItems: TodoItem[] = [
+ { id: '1', text: '법인 설립 안내 드리기', checked: false },
+ { id: '2', text: '법인 설립 혹은 변경 등기 비용 안내 드리기', checked: false },
+ { id: '3', text: '입력해주신 정보를 바탕으로 등기신청서 제...', checked: true },
+];
+
+const completedItems: TodoItem[] = [
+ { id: '1', text: '법인 설립 안내 드리기', checked: true },
+ { id: '2', text: '법인 설립 혹은 변경 등기 비용 안내 드리기', checked: true },
+ { id: '3', text: '입력해주신 정보를 바탕으로 등기신청서 제...', checked: true },
+];
+
+const meta = {
+ title: 'Components/TodoCard',
+ component: TodoCard,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ args: {
+ title: '법인 설립',
+ items: sampleItems,
+ onKebabClick: fn(),
+ },
+ argTypes: {
+ expanded: { control: 'boolean' },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const ControlledTodoCard = (args: ComponentProps) => {
+ const [items, setItems] = useState(args.items);
+
+ const handleCheckedChange = (id: string, checked: boolean) => {
+ setItems((prev) => prev.map((item) => (item.id === id ? { ...item, checked } : item)));
+ };
+
+ return ;
+};
+
+export const Default: Story = {
+ render: (args) => ,
+};
+
+export const AllCompleted: Story = {
+ render: (args) => ,
+ args: {
+ items: completedItems,
+ },
+};
+
+export const Collapsed: Story = {
+ render: (args) => ,
+ args: {
+ expanded: false,
+ },
+};
+
+export const CollapsedCompleted: Story = {
+ render: (args) => ,
+ args: {
+ items: completedItems,
+ expanded: false,
+ },
+};
+
+export const Overview: Story = {
+ render: (args) => {
+ const [items1, setItems1] = useState(sampleItems);
+ const [items2, setItems2] = useState(completedItems);
+
+ return (
+
+
+ setItems1((prev) => prev.map((item) => (item.id === id ? { ...item, checked } : item)))
+ }
+ />
+
+ setItems2((prev) => prev.map((item) => (item.id === id ? { ...item, checked } : item)))
+ }
+ />
+
+
+
+ );
+ },
+ parameters: {
+ controls: { disable: true },
+ },
+};
diff --git a/src/components/todo-card/TodoCard.tsx b/src/components/todo-card/TodoCard.tsx
new file mode 100644
index 0000000..f52657f
--- /dev/null
+++ b/src/components/todo-card/TodoCard.tsx
@@ -0,0 +1,61 @@
+'use client';
+
+import clsx from 'clsx';
+import Image from 'next/image';
+
+import Badge from '@/components/badge/Badge';
+import CheckBox from '@/components/checkbox/CheckBox';
+
+import styles from './styles/TodoCard.module.css';
+import { TODO_CARD_ICONS } from './constants/todoCardConstants';
+import type { TodoCardProps } from './types/types';
+
+/**
+ * 할일 카드 컴포넌트.
+ * 제목, 진행 상태 뱃지, 체크박스 리스트를 포함합니다.
+ * expanded가 false이면 헤더만 표시됩니다.
+ */
+export default function TodoCard({
+ title,
+ items,
+ onItemCheckedChange,
+ onKebabClick,
+ expanded = true,
+ className,
+}: TodoCardProps) {
+ const checkedCount = items.filter((item) => item.checked).length;
+ const totalCount = items.length;
+ const badgeLabel = `${checkedCount}/${totalCount}`;
+ const badgeState = checkedCount === totalCount && totalCount > 0 ? 'done' : 'ongoing';
+
+ return (
+
+
+ {title}
+
+
+
+
+ {expanded && items.length > 0 && (
+
+ {items.map((item) => (
+
+ {item.text}}
+ onCheckedChange={
+ onItemCheckedChange
+ ? (checked) => onItemCheckedChange(item.id, checked)
+ : undefined
+ }
+ />
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/components/todo-card/constants/todoCardConstants.ts b/src/components/todo-card/constants/todoCardConstants.ts
new file mode 100644
index 0000000..de08e04
--- /dev/null
+++ b/src/components/todo-card/constants/todoCardConstants.ts
@@ -0,0 +1,5 @@
+import kebabSmall from '@/assets/icons/kebab/kebabSmall.svg';
+
+export const TODO_CARD_ICONS = {
+ kebab: kebabSmall,
+} as const;
diff --git a/src/components/todo-card/index.ts b/src/components/todo-card/index.ts
new file mode 100644
index 0000000..a5756de
--- /dev/null
+++ b/src/components/todo-card/index.ts
@@ -0,0 +1,2 @@
+export { default as TodoCard } from './TodoCard';
+export type { TodoCardProps, TodoItem } from './types/types';
diff --git a/src/components/todo-card/styles/TodoCard.module.css b/src/components/todo-card/styles/TodoCard.module.css
new file mode 100644
index 0000000..933cafc
--- /dev/null
+++ b/src/components/todo-card/styles/TodoCard.module.css
@@ -0,0 +1,73 @@
+.card {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 12px;
+ border-radius: 12px;
+ background-color: var(--color-background-inverse);
+ box-shadow:
+ 0 1px 2px rgba(0, 0, 0, 0.06),
+ 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.title {
+ flex: 1;
+ min-width: 0;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.4;
+ color: var(--color-text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.kebab {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border: none;
+ background: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.kebab:hover {
+ background-color: var(--color-background-tertiary);
+}
+
+.body {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.item {
+ display: flex;
+ align-items: center;
+}
+
+.itemLabel {
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 1.4;
+ color: var(--color-text-secondary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.itemChecked .itemLabel {
+ color: var(--color-text-disabled);
+ text-decoration: line-through;
+}
diff --git a/src/components/todo-card/types/types.ts b/src/components/todo-card/types/types.ts
new file mode 100644
index 0000000..45e1fa7
--- /dev/null
+++ b/src/components/todo-card/types/types.ts
@@ -0,0 +1,23 @@
+export type TodoItem = {
+ /** 항목 고유 ID */
+ id: string;
+ /** 항목 텍스트 */
+ text: string;
+ /** 체크 여부 */
+ checked: boolean;
+};
+
+export type TodoCardProps = {
+ /** 카드 제목 */
+ title: string;
+ /** 체크박스 항목 목록 */
+ items: TodoItem[];
+ /** 항목 체크 상태 변경 시 호출되는 콜백 */
+ onItemCheckedChange?: (id: string, checked: boolean) => void;
+ /** 케밥(⋮) 버튼 클릭 시 호출되는 콜백 */
+ onKebabClick?: () => void;
+ /** 체크리스트 펼침 여부 (기본값: true) */
+ expanded?: boolean;
+ /** 추가 CSS 클래스 */
+ className?: string;
+};