diff --git a/src/hooks/useCounter/index.ts b/src/hooks/useCounter/index.ts
new file mode 100644
index 00000000..1e13a46a
--- /dev/null
+++ b/src/hooks/useCounter/index.ts
@@ -0,0 +1 @@
+export { useCounter } from './useCounter.ts';
diff --git a/src/hooks/useCounter/ko/useCounter.md b/src/hooks/useCounter/ko/useCounter.md
new file mode 100644
index 00000000..71b15d0b
--- /dev/null
+++ b/src/hooks/useCounter/ko/useCounter.md
@@ -0,0 +1,120 @@
+# useCounter
+
+`useCounter`는 증가, 감소, 초기화 기능을 갖춘 숫자형 카운터 상태를 관리하는 리액트 훅이에요. 선택적으로 최소값과 최대값을 제공하여 카운터의 범위를 제한할 수 있어요.
+
+## 인터페이스
+
+```ts
+function useCounter(options?: UseCounterOptions): UseCounterReturn;
+
+type UseCounterOptions = {
+ initialValue?: number;
+ min?: number;
+ max?: number;
+ step?: number;
+};
+ㄴ
+type UseCounterReturn = {
+ count: number;
+ increment: () => void;
+ decrement: () => void;
+ reset: () => void;
+ setCount: (value: number | ((prev: number) => number)) => void;
+};
+```
+
+### 파라미터
+
+
+
+### 반환 값
+
+
+
+## 예시
+
+```tsx
+import { useCounter } from 'react-simplikit';
+
+function ShoppingCart() {
+ const { count, increment, decrement, reset } = useCounter({
+ initialValue: 1,
+ min: 1,
+ max: 10,
+ });
+
+ return (
+
+ 수량: {count}
+
+
+
+
+ );
+}
+```
+
+## 제약 조건
+
+이 훅은 카운터가 지정된 범위 내에서 유지되도록 자동으로 보장해요:
+
+- 값이 `max`보다 커지면 자동으로 `max` 값으로 제한돼요.
+- 값이 `min`보다 작아지면 자동으로 `min` 값으로 제한돼요.
+- `setCount`를 사용할 때 범위를 벗어나는 값은 자동으로 가장 가까운 경계값으로 조정돼요.
diff --git a/src/hooks/useCounter/useCounter.md b/src/hooks/useCounter/useCounter.md
new file mode 100644
index 00000000..fa5ed032
--- /dev/null
+++ b/src/hooks/useCounter/useCounter.md
@@ -0,0 +1,120 @@
+# useCounter
+
+`useCounter` is a React hook that manages a numeric counter state with increment, decrement, and reset capabilities. Optionally, you can provide minimum and maximum values to constrain the counter's range.
+
+## Interface
+
+```ts
+function useCounter(options?: UseCounterOptions): UseCounterReturn;
+
+type UseCounterOptions = {
+ initialValue?: number;
+ min?: number;
+ max?: number;
+ step?: number;
+};
+
+type UseCounterReturn = {
+ count: number;
+ increment: () => void;
+ decrement: () => void;
+ reset: () => void;
+ setCount: (value: number | ((prev: number) => number)) => void;
+};
+```
+
+### Parameters
+
+
+
+### Return Value
+
+
+
+## Example
+
+```tsx
+import { useCounter } from 'react-simplikit';
+
+function ShoppingCart() {
+ const { count, increment, decrement, reset } = useCounter({
+ initialValue: 1,
+ min: 1,
+ max: 10,
+ });
+
+ return (
+
+ Quantity: {count}
+
+
+
+
+ );
+}
+```
+
+## Constraints
+
+The hook automatically ensures that the counter stays within the specified bounds:
+
+- When incrementing beyond `max`, the value will stay at `max`
+- When decrementing below `min`, the value will stay at `min`
+- When using `setCount`, any value outside the bounds will be adjusted to the nearest boundary
diff --git a/src/hooks/useCounter/useCounter.spec.ts b/src/hooks/useCounter/useCounter.spec.ts
new file mode 100644
index 00000000..6141d1af
--- /dev/null
+++ b/src/hooks/useCounter/useCounter.spec.ts
@@ -0,0 +1,187 @@
+import { act, renderHook } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+
+import { useCounter } from './useCounter.ts';
+
+describe('useCounter', () => {
+ it('should initialize with default value', () => {
+ const { result } = renderHook(() => useCounter());
+
+ expect(result.current.count).toBe(0);
+ });
+
+ it('should initialize with provided initial value', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 10,
+ })
+ );
+
+ expect(result.current.count).toBe(10);
+ });
+
+ it('should increment the counter', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ })
+ );
+
+ act(() => {
+ result.current.increment();
+ });
+
+ expect(result.current.count).toBe(6);
+ });
+
+ it('should decrement the counter', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ })
+ );
+
+ act(() => {
+ result.current.decrement();
+ });
+
+ expect(result.current.count).toBe(4);
+ });
+
+ it('should reset the counter to initial value', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ })
+ );
+
+ act(() => {
+ result.current.increment();
+ result.current.increment();
+ });
+
+ expect(result.current.count).toBe(7);
+
+ act(() => {
+ result.current.reset();
+ });
+
+ expect(result.current.count).toBe(5);
+ });
+
+ it('should not go below minimum value', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ min: 3,
+ })
+ );
+
+ act(() => {
+ result.current.decrement();
+ result.current.decrement();
+ result.current.decrement();
+ });
+
+ expect(result.current.count).toBe(3);
+ });
+
+ it('should not go above maximum value', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ max: 7,
+ })
+ );
+
+ act(() => {
+ result.current.increment();
+ result.current.increment();
+ result.current.increment();
+ });
+
+ expect(result.current.count).toBe(7);
+ });
+
+ it('should use the provided step value for increment and decrement', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ step: 2,
+ })
+ );
+
+ act(() => {
+ result.current.increment();
+ });
+
+ expect(result.current.count).toBe(7);
+
+ act(() => {
+ result.current.decrement();
+ });
+
+ expect(result.current.count).toBe(5);
+ });
+
+ it('should adjust initial value to match constraints', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 1,
+ min: 3,
+ })
+ );
+
+ expect(result.current.count).toBe(3);
+
+ const { result: result2 } = renderHook(() =>
+ useCounter({
+ initialValue: 10,
+ max: 8,
+ })
+ );
+
+ expect(result2.current.count).toBe(8);
+ });
+
+ it('should allow setting arbitrary value within constraints', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ min: 3,
+ max: 8,
+ })
+ );
+
+ act(() => {
+ result.current.setCount(6);
+ });
+
+ expect(result.current.count).toBe(6);
+
+ act(() => {
+ result.current.setCount(1);
+ });
+
+ expect(result.current.count).toBe(3);
+
+ act(() => {
+ result.current.setCount(10);
+ });
+
+ expect(result.current.count).toBe(8);
+ });
+
+ it('should work with updater function for setCount', () => {
+ const { result } = renderHook(() =>
+ useCounter({
+ initialValue: 5,
+ })
+ );
+
+ act(() => {
+ result.current.setCount(prev => prev + 3);
+ });
+
+ expect(result.current.count).toBe(8);
+ });
+});
diff --git a/src/hooks/useCounter/useCounter.ts b/src/hooks/useCounter/useCounter.ts
new file mode 100644
index 00000000..4a132808
--- /dev/null
+++ b/src/hooks/useCounter/useCounter.ts
@@ -0,0 +1,100 @@
+import { useCallback, useState } from 'react';
+
+type UseCounterOptions = {
+ initialValue?: number;
+ min?: number;
+ max?: number;
+ step?: number;
+};
+
+type UseCounterReturn = {
+ count: number;
+ increment: () => void;
+ decrement: () => void;
+ reset: () => void;
+ setCount: (value: number | ((prev: number) => number)) => void;
+};
+
+/**
+ * @description
+ * `useCounter` is a React hook that manages a numeric counter state with increment, decrement, and reset capabilities.
+ * Optionally, you can provide minimum and maximum values to constrain the counter's range.
+ *
+ * @param {UseCounterOptions} options - The options for the counter.
+ * @param {number} [options.initialValue=0] - Initial value for the counter. Defaults to 0.
+ * @param {number} [options.min] - Minimum value the counter can reach. If not provided, there is no lower limit.
+ * @param {number} [options.max] - Maximum value the counter can reach. If not provided, there is no upper limit.
+ * @param {number} [options.step=1] - Value to increment or decrement by. Defaults to 1.
+ *
+ * @returns {UseCounterReturn} An object with count value and control functions.
+ *
+ * @example
+ * import { useCounter } from 'react-simplikit';
+ *
+ * function ShoppingCart() {
+ * const { count, increment, decrement, reset } = useCounter({
+ * initialValue: 1,
+ * min: 1,
+ * max: 10,
+ * });
+ *
+ * return (
+ *
+ * Quantity: {count}
+ *
+ *
+ *
+ *
+ * );
+ * }
+ */
+export function useCounter({ initialValue = 0, min, max, step = 1 }: UseCounterOptions = {}): UseCounterReturn {
+ const validateValue = (value: number): number => {
+ let validatedValue = value;
+
+ if (min !== undefined && validatedValue < min) {
+ validatedValue = min;
+ }
+
+ if (max !== undefined && validatedValue > max) {
+ validatedValue = max;
+ }
+
+ return validatedValue;
+ };
+
+ const [count, setCountState] = useState(() => validateValue(initialValue));
+
+ const validateValueMemoized = useCallback(validateValue, [min, max]);
+
+ const setCount = useCallback(
+ (value: number | ((prev: number) => number)) => {
+ setCountState(prev => {
+ const nextValue = typeof value === 'function' ? value(prev) : value;
+
+ return validateValueMemoized(nextValue);
+ });
+ },
+ [validateValueMemoized]
+ );
+
+ const increment = useCallback(() => {
+ setCount(prev => prev + step);
+ }, [setCount, step]);
+
+ const decrement = useCallback(() => {
+ setCount(prev => prev - step);
+ }, [setCount, step]);
+
+ const reset = useCallback(() => {
+ setCount(initialValue);
+ }, [setCount, initialValue]);
+
+ return {
+ count,
+ increment,
+ decrement,
+ reset,
+ setCount,
+ };
+}
diff --git a/src/index.ts b/src/index.ts
index 36bed094..a6bc83c8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,6 +4,7 @@ export { SwitchCase } from './components/SwitchCase/index.ts';
export { useAsyncEffect } from './hooks/useAsyncEffect/index.ts';
export { useBooleanState } from './hooks/useBooleanState/index.ts';
export { useCallbackOncePerRender } from './hooks/useCallbackOncePerRender/index.ts';
+export { useCounter } from './hooks/useCounter/index.ts';
export { useDebounce } from './hooks/useDebounce/index.ts';
export { useImpressionRef } from './hooks/useImpressionRef/index.ts';
export { useInputState } from './hooks/useInputState/index.ts';