diff --git a/src/assets/icons/check/nonChekcedLarge.svg b/src/assets/icons/check/nonCheckedLarge.svg
similarity index 100%
rename from src/assets/icons/check/nonChekcedLarge.svg
rename to src/assets/icons/check/nonCheckedLarge.svg
diff --git a/src/assets/icons/check/nonChekcedSmall.svg b/src/assets/icons/check/nonCheckedSmall.svg
similarity index 100%
rename from src/assets/icons/check/nonChekcedSmall.svg
rename to src/assets/icons/check/nonCheckedSmall.svg
diff --git a/src/components/checkbox/CheckBox.tsx b/src/components/checkbox/CheckBox.tsx
new file mode 100644
index 0000000..879f1d1
--- /dev/null
+++ b/src/components/checkbox/CheckBox.tsx
@@ -0,0 +1,101 @@
+'use client';
+
+import clsx from 'clsx';
+import Image from 'next/image';
+import type { ChangeEvent, CSSProperties, KeyboardEvent, MouseEvent } from 'react';
+
+import styles from './styles/CheckBox.module.css';
+import { CHECKBOX_STYLE } from './constants/styleConstants';
+import type { CheckBoxProps } from './types/types';
+
+/**
+ * 체크박스 컴포넌트.
+ * @param checked 체크 여부
+ * @param onCheckedChange 체크 상태 변경 콜백
+ * @param size 체크박스 크기('large' | 'small')
+ * @param label 접근성 용도의 라벨(없으면 options.ariaLabel 필수)
+ * @param options 고급 옵션(ariaLabel/readOnly/icons)
+ */
+export default function CheckBox({
+ checked,
+ size = 'large',
+ label,
+ id,
+ name,
+ value,
+ disabled = false,
+ className,
+ options,
+ onCheckedChange,
+}: CheckBoxProps) {
+const hasLabel = label != null && (typeof label === 'string' ? label.trim() !== '' : true);
+ const isReadOnly = options?.readOnly || !onCheckedChange;
+ const isDisabled = disabled;
+ const iconSrc = checked
+ ? CHECKBOX_STYLE.icons.checked[size]
+ : CHECKBOX_STYLE.icons.unchecked[size];
+ const boxSize = CHECKBOX_STYLE.boxSize[size];
+ const inputAriaLabel = hasLabel ? undefined : options?.ariaLabel;
+ const checkboxStyle = {
+ '--checkbox-box-size': `${boxSize}px`,
+ } as CSSProperties;
+ const iconNode = options?.icons ? (
+ checked ? (
+ options.icons.checked
+ ) : (
+ options.icons.unchecked
+ )
+ ) : (
+
+ );
+
+ const handleClick = (event: MouseEvent) => {
+ if (isReadOnly) {
+ event.preventDefault();
+ }
+ };
+
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (isReadOnly && (event.key === ' ' || event.key === 'Enter')) {
+ event.preventDefault();
+ }
+ };
+
+ const handleChange = (event: ChangeEvent) => {
+ if (isReadOnly || isDisabled) {
+ event.preventDefault();
+ return;
+ }
+ onCheckedChange?.(event.target.checked);
+ };
+
+ if (process.env.NODE_ENV !== 'production' && !hasLabel && !options?.ariaLabel) {
+ console.warn('CheckBox: label이 비어있다면 ariaLabel이 필요합니다.');
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/checkbox/constants/styleConstants.ts b/src/components/checkbox/constants/styleConstants.ts
new file mode 100644
index 0000000..9074ba7
--- /dev/null
+++ b/src/components/checkbox/constants/styleConstants.ts
@@ -0,0 +1,21 @@
+import checkedLarge from '@/assets/icons/check/checkedLarge.svg';
+import checkedSmall from '@/assets/icons/check/checkedSmall.svg';
+import nonCheckedLarge from '@/assets/icons/check/nonCheckedLarge.svg';
+import nonCheckedSmall from '@/assets/icons/check/nonCheckedSmall.svg';
+
+export const CHECKBOX_STYLE = {
+ icons: {
+ checked: {
+ large: checkedLarge,
+ small: checkedSmall,
+ },
+ unchecked: {
+ large: nonCheckedLarge,
+ small: nonCheckedSmall,
+ },
+ },
+ boxSize: {
+ large: 18,
+ small: 16,
+ },
+} as const;
diff --git a/src/components/checkbox/styles/CheckBox.module.css b/src/components/checkbox/styles/CheckBox.module.css
new file mode 100644
index 0000000..937baeb
--- /dev/null
+++ b/src/components/checkbox/styles/CheckBox.module.css
@@ -0,0 +1,60 @@
+.checkbox {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+}
+
+.large {
+ font-size: 16px;
+}
+
+.small {
+ font-size: 14px;
+}
+
+.input {
+ position: absolute;
+ opacity: 0;
+ width: 1px;
+ height: 1px;
+ pointer-events: none;
+}
+
+.box {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--checkbox-box-size, 18px);
+ height: var(--checkbox-box-size, 18px);
+ flex-shrink: 0;
+}
+
+.icon {
+ width: 100%;
+ height: 100%;
+ display: block;
+}
+
+.label {
+ line-height: 1.4;
+}
+
+.disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+
+@media (max-width: 480px) {
+ .checkbox {
+ gap: 6px;
+ }
+
+ .large {
+ font-size: 15px;
+ }
+
+ .small {
+ font-size: 13px;
+ }
+}
diff --git a/src/components/checkbox/types/types.ts b/src/components/checkbox/types/types.ts
new file mode 100644
index 0000000..2afe493
--- /dev/null
+++ b/src/components/checkbox/types/types.ts
@@ -0,0 +1,39 @@
+import type { ReactNode } from 'react';
+
+export type CheckBoxSize = 'large' | 'small';
+
+export type CheckBoxIconSet = {
+ checked: ReactNode;
+ unchecked: ReactNode;
+};
+
+export type CheckBoxOptions = {
+ ariaLabel?: string;
+ readOnly?: boolean;
+ icons?: CheckBoxIconSet;
+};
+
+export type CheckBoxOptionsWithAriaLabel = Omit & {
+ ariaLabel: string;
+};
+
+interface CheckBoxBaseProps {
+ checked: boolean;
+ size?: CheckBoxSize;
+ id?: string;
+ name?: string;
+ value?: string;
+ disabled?: boolean;
+ className?: string;
+ onCheckedChange?: (checked: boolean) => void;
+}
+
+export type CheckBoxProps =
+ | (CheckBoxBaseProps & {
+ label: ReactNode;
+ options?: CheckBoxOptions;
+ })
+ | (CheckBoxBaseProps & {
+ label?: undefined;
+ options: CheckBoxOptionsWithAriaLabel;
+ });
diff --git a/src/types/svg.d.ts b/src/types/svg.d.ts
new file mode 100644
index 0000000..b49d15d
--- /dev/null
+++ b/src/types/svg.d.ts
@@ -0,0 +1,6 @@
+declare module '*.svg' {
+ import type { StaticImageData } from 'next/image';
+
+ const src: StaticImageData;
+ export default src;
+}
diff --git a/tsconfig.json b/tsconfig.json
index cf9c65d..6082465 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,6 +24,7 @@
},
"include": [
"next-env.d.ts",
+ "**/*.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",