diff --git a/src/assets/icons/state/stateEmptyLarge.svg b/src/assets/icons/state/stateEmptyLarge.svg
new file mode 100644
index 0000000..c96c95e
--- /dev/null
+++ b/src/assets/icons/state/stateEmptyLarge.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/state/stateEmptySmall.svg b/src/assets/icons/state/stateEmptySmall.svg
new file mode 100644
index 0000000..250e4bb
--- /dev/null
+++ b/src/assets/icons/state/stateEmptySmall.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/badge/Badge.stories.tsx b/src/components/badge/Badge.stories.tsx
new file mode 100644
index 0000000..3be9eda
--- /dev/null
+++ b/src/components/badge/Badge.stories.tsx
@@ -0,0 +1,78 @@
+import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+
+import Badge from './Badge';
+
+const meta = {
+ title: 'Components/Badge',
+ component: Badge,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ args: {
+ state: 'done',
+ label: '5/5',
+ },
+ argTypes: {
+ state: {
+ control: 'inline-radio',
+ options: ['done', 'ongoing', 'empty'],
+ },
+ size: {
+ control: 'inline-radio',
+ options: ['small', 'large'],
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Done: Story = {
+ args: {
+ state: 'done',
+ label: '5/5',
+ },
+};
+
+export const Ongoing: Story = {
+ args: {
+ state: 'ongoing',
+ label: '3/5',
+ },
+};
+
+export const Empty: Story = {
+ args: {
+ state: 'empty',
+ label: '0/5',
+ },
+};
+
+export const Large: Story = {
+ args: {
+ state: 'done',
+ size: 'large',
+ label: '5/5',
+ },
+};
+
+export const Overview: Story = {
+ render: () => (
+
+ ),
+ parameters: {
+ controls: { disable: true },
+ },
+};
diff --git a/src/components/badge/Badge.tsx b/src/components/badge/Badge.tsx
new file mode 100644
index 0000000..d228073
--- /dev/null
+++ b/src/components/badge/Badge.tsx
@@ -0,0 +1,27 @@
+import clsx from 'clsx';
+import Image from 'next/image';
+
+import styles from './styles/Badge.module.css';
+import { BADGE_STATE_LABEL, BADGE_STYLE } from './constants/badgeConstants';
+import type { BadgeProps } from './types/types';
+
+/**
+ * 상태 표시 뱃지 컴포넌트.
+ * state에 따라 색상과 아이콘이 자동 결정되며 (done=초록, ongoing=파랑, empty=회색),
+ * 스크린리더용 aria-label도 자동으로 "완료: 라벨", "진행 중: 라벨" 형태로 생성됩니다.
+ */
+export default function Badge({ state, size = 'small', label }: BadgeProps) {
+ const iconSrc = BADGE_STYLE.icons[state][size];
+ const iconSize = BADGE_STYLE.iconSize[size];
+
+ return (
+
+
+ {label}
+
+ );
+}
diff --git a/src/components/badge/constants/badgeConstants.ts b/src/components/badge/constants/badgeConstants.ts
new file mode 100644
index 0000000..46c8373
--- /dev/null
+++ b/src/components/badge/constants/badgeConstants.ts
@@ -0,0 +1,23 @@
+import type { BadgeState } from '../types/types';
+
+import stateDoneLarge from '@/assets/icons/state/stateDoneLarge.svg';
+import stateDoneSmall from '@/assets/icons/state/stateDoneSmall.svg';
+import stateOngoingLarge from '@/assets/icons/state/stateOngoingLarge.svg';
+import stateOngoingSmall from '@/assets/icons/state/stateOngoingSmall.svg';
+import stateEmptyLarge from '@/assets/icons/state/stateEmptyLarge.svg';
+import stateEmptySmall from '@/assets/icons/state/stateEmptySmall.svg';
+
+export const BADGE_STATE_LABEL: Record = {
+ done: '완료',
+ ongoing: '진행 중',
+ empty: '시작 전',
+} as const;
+
+export const BADGE_STYLE = {
+ icons: {
+ done: { large: stateDoneLarge, small: stateDoneSmall },
+ ongoing: { large: stateOngoingLarge, small: stateOngoingSmall },
+ empty: { large: stateEmptyLarge, small: stateEmptySmall },
+ },
+ iconSize: { large: 20, small: 16 },
+} as const;
diff --git a/src/components/badge/index.ts b/src/components/badge/index.ts
new file mode 100644
index 0000000..bcd3195
--- /dev/null
+++ b/src/components/badge/index.ts
@@ -0,0 +1,2 @@
+export { default as Badge } from './Badge';
+export type { BadgeProps, BadgeState, BadgeSize } from './types/types';
diff --git a/src/components/badge/styles/Badge.module.css b/src/components/badge/styles/Badge.module.css
new file mode 100644
index 0000000..aef54e0
--- /dev/null
+++ b/src/components/badge/styles/Badge.module.css
@@ -0,0 +1,34 @@
+.badge {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ background-color: var(--color-background-inverse);
+}
+
+.large {
+ padding: 4px 10px 4px 6px;
+ gap: 4px;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.small {
+ padding: 2px 8px 2px 4px;
+ gap: 3px;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.done,
+.ongoing {
+ color: var(--color-icon-brand);
+}
+
+.empty {
+ color: var(--color-text-disabled);
+}
+
+.icon {
+ display: block;
+ flex-shrink: 0;
+}
diff --git a/src/components/badge/types/types.ts b/src/components/badge/types/types.ts
new file mode 100644
index 0000000..9e69a10
--- /dev/null
+++ b/src/components/badge/types/types.ts
@@ -0,0 +1,14 @@
+/** 뱃지 상태. `done`(완료) | `ongoing`(진행 중) | `empty`(시작 전) */
+export type BadgeState = 'done' | 'ongoing' | 'empty';
+
+/** 뱃지 크기. `large`(아이콘 20px) | `small`(아이콘 16px) */
+export type BadgeSize = 'large' | 'small';
+
+export type BadgeProps = {
+ /** 뱃지 상태 (색상·아이콘이 자동 결정됨) */
+ state: BadgeState;
+ /** 뱃지 크기 (기본값: `'small'`) */
+ size?: BadgeSize;
+ /** 뱃지에 표시할 텍스트 (예: "3개" , "마감 완료") */
+ label: string;
+};