diff --git a/.changeset/tall-steaks-melt.md b/.changeset/tall-steaks-melt.md
new file mode 100644
index 000000000..f4a212f4d
--- /dev/null
+++ b/.changeset/tall-steaks-melt.md
@@ -0,0 +1,5 @@
+---
+'@modern-kit/react': minor
+---
+
+feat(react): useDocumentTitle 커스텀 훅 추가 - @ssi02014
diff --git a/docs/docs/react/hooks/useDoumentTitle.mdx b/docs/docs/react/hooks/useDoumentTitle.mdx
new file mode 100644
index 000000000..2fd36b858
--- /dev/null
+++ b/docs/docs/react/hooks/useDoumentTitle.mdx
@@ -0,0 +1,82 @@
+import { useDocumentTitle } from '@modern-kit/react';
+import { useState } from 'react';
+
+# useDocumentTitle
+
+`SEO`와는 관계 없이 `document.title`을 동적으로 변경시켜주는 커스텀 훅입니다.
+
+`preserveTitleOnUnmount` 옵션을 `true`로 준다면 `unmount` 시에 변경 된 타이틀로 유지할 수 있습니다.
+
+
+
+## Code
+[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/react/src/hooks/useDocumentTitle/index.ts)
+
+## Interface
+```ts title="typescript"
+interface UseDocumentTitleOption {
+ preserveTitleOnUnmount?: boolean; // default: false
+}
+
+const useDocumentTitle: (
+ title: string,
+ { preserveTitleOnUnmount }?: UseDocumentTitleOption
+) => void;
+```
+
+## Usage
+```tsx title="typescript"
+import { useState } from 'react';
+import { useDocumentTitle } from '@modern-kit/react';
+
+const Example = () => {
+ const [title, setTitle] = useState('useDocumentTitle');
+ const [inputValue, setInputValue] = useState('');
+
+ const handleChangeTitle = () => {
+ setTitle(inputValue);
+ alert('타이틀이 변경됐습니다.');
+ };
+
+ useDocumentTitle(title, {
+ preserveTitleOnUnmount: false, // default: false
+ });
+
+ return (
+
+ setInputValue(e.target.value)}
+ />
+
+
+ );
+};
+```
+
+## Example
+
+export const Example = () => {
+ const [title, setTitle] = useState('useDocumentTitle');
+ const [inputValue, setInputValue] = useState('');
+ const handleChangeTitle = () => {
+ setTitle(inputValue);
+ alert('타이틀이 변경됐습니다.');
+ };
+ useDocumentTitle(title, {
+ preserveTitleOnUnmount: false, // default: false
+ });
+ return (
+
+ setInputValue(e.target.value)}
+ />
+
+
+ );
+};
+
+
\ No newline at end of file
diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts
index ce3f0002a..528b9ba3b 100644
--- a/packages/react/src/hooks/index.ts
+++ b/packages/react/src/hooks/index.ts
@@ -3,6 +3,7 @@ export * from './useAsyncProcessQueue';
export * from './useBlockPromiseMultipleClick';
export * from './useClipboard';
export * from './useDebounce';
+export * from './useDocumentTitle';
export * from './useFileReader';
export * from './useForceUpdate';
export * from './useImageStatus';
diff --git a/packages/react/src/hooks/useDocumentTitle/index.ts b/packages/react/src/hooks/useDocumentTitle/index.ts
new file mode 100644
index 000000000..3acd6d70d
--- /dev/null
+++ b/packages/react/src/hooks/useDocumentTitle/index.ts
@@ -0,0 +1,21 @@
+import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';
+
+interface UseDocumentTitleOption {
+ preserveTitleOnUnmount?: boolean;
+}
+
+export const useDocumentTitle = (
+ title: string,
+ { preserveTitleOnUnmount = false }: UseDocumentTitleOption = {}
+) => {
+ useIsomorphicLayoutEffect(() => {
+ const prevTitle = document.title;
+ document.title = title;
+
+ return () => {
+ if (!preserveTitleOnUnmount) {
+ document.title = prevTitle;
+ }
+ };
+ }, [title, preserveTitleOnUnmount]);
+};
diff --git a/packages/react/src/hooks/useDocumentTitle/useDoucmentTitle.spec.ts b/packages/react/src/hooks/useDocumentTitle/useDoucmentTitle.spec.ts
new file mode 100644
index 000000000..1698ac2be
--- /dev/null
+++ b/packages/react/src/hooks/useDocumentTitle/useDoucmentTitle.spec.ts
@@ -0,0 +1,55 @@
+import { renderHook } from '@testing-library/react';
+import { useDocumentTitle } from '.';
+
+const ORIGIN_TITLE = 'origin title';
+const FIRST_CHANGE_TITLE = 'first change title';
+const SECOND_CHANGE_TITLE = 'second change title';
+
+beforeEach(() => {
+ document.title = ORIGIN_TITLE;
+});
+
+describe('useDocumentTitle', () => {
+ it('should update the document title', () => {
+ const { rerender, unmount } = renderHook(
+ ({ title }) => useDocumentTitle(title),
+ {
+ initialProps: { title: FIRST_CHANGE_TITLE },
+ }
+ );
+
+ expect(document.title).toBe(FIRST_CHANGE_TITLE);
+
+ rerender({ title: SECOND_CHANGE_TITLE });
+
+ expect(document.title).toBe(SECOND_CHANGE_TITLE);
+
+ unmount();
+
+ expect(document.title).toBe(ORIGIN_TITLE);
+ });
+
+ it('should revert to the original title on unmount if preserveTitleOnUnmount is false', () => {
+ const { unmount } = renderHook(() =>
+ useDocumentTitle(FIRST_CHANGE_TITLE, { preserveTitleOnUnmount: false })
+ );
+
+ expect(document.title).toBe(FIRST_CHANGE_TITLE);
+
+ unmount();
+
+ expect(document.title).toBe(ORIGIN_TITLE);
+ });
+
+ it('should retain the changed title on unmount if preserveTitleOnUnmount is true', () => {
+ const { unmount } = renderHook(() =>
+ useDocumentTitle(FIRST_CHANGE_TITLE, { preserveTitleOnUnmount: true })
+ );
+
+ expect(document.title).toBe(FIRST_CHANGE_TITLE);
+
+ unmount();
+
+ expect(document.title).toBe(FIRST_CHANGE_TITLE);
+ });
+});