diff --git a/src/hooks/useContentRect/useContentRect.mdx b/src/hooks/useContentRect/useContentRect.mdx
new file mode 100644
index 0000000..db4ed6a
--- /dev/null
+++ b/src/hooks/useContentRect/useContentRect.mdx
@@ -0,0 +1,45 @@
+import { Meta } from '@storybook/blocks';
+
+
+
+# useContentRect
+
+The `useContentRect` hook is used to get the size of an HTML element, the `DOMRect` is stored in a
+`RefObject`. It uses the `ResizeObserver` API to observe the element `DOMRect`, the target is
+disconnected when the component unmounts.
+
+## Reference
+
+```ts
+function useContentRect(ref: Unreffable): RefObject;
+```
+
+## Using `useContentRect` with a RefObject
+
+In this example, the `useRef` hook is used to create a `RefObject` for the `div` element which is
+initially set to null. The `useContentRect` hook is then called with the ref variable.
+
+```tsx
+function MyComponent() {
+ const ref = useRef(null);
+
+ const contentRectRef = useContentRect(ref);
+
+ return ;
+}
+```
+
+## Using `useContentRect` with an element
+
+In this example, the `useState` hook is used to create a state variable element which is initially
+set to null. The `useContentRect` hook is then called with the element variable.
+
+```tsx
+function MyComponent() {
+ const [element, setElement] = useState(null);
+
+ const contentRectRef = useContentRect(element);
+
+ return ;
+}
+```
diff --git a/src/hooks/useContentRect/useContentRect.stories.tsx b/src/hooks/useContentRect/useContentRect.stories.tsx
new file mode 100644
index 0000000..d08c802
--- /dev/null
+++ b/src/hooks/useContentRect/useContentRect.stories.tsx
@@ -0,0 +1,67 @@
+/* eslint-disable react/jsx-no-literals, react-hooks/rules-of-hooks */
+import type { Meta, StoryObj } from '@storybook/react';
+import { useEffect, useRef } from 'react';
+import { useForceRerender } from '../../index.js';
+import { useContentRect } from './useContentRect.js';
+
+const meta = {
+ title: 'Hooks / useContentRect',
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const UseContentRect: Story = {
+ render() {
+ const onClick = useForceRerender();
+ const ref = useRef(null);
+ const contentRectRef = useContentRect(ref);
+
+ useEffect(() => {
+ const animation = ref.current?.animate(
+ [
+ // keyframes
+ { inlineSize: '300px', blockSize: '300px' },
+ { inlineSize: '500px', blockSize: '500px' },
+ { inlineSize: '300px', blockSize: '300px' },
+ ],
+ {
+ // timing options
+ duration: 2000,
+ iterations: Number.POSITIVE_INFINITY,
+ },
+ );
+
+ return () => animation?.cancel();
+ }, []);
+
+ return (
+ <>
+
+ >
+ );
+ },
+};
diff --git a/src/hooks/useContentRect/useContentRect.ts b/src/hooks/useContentRect/useContentRect.ts
new file mode 100644
index 0000000..a6718bf
--- /dev/null
+++ b/src/hooks/useContentRect/useContentRect.ts
@@ -0,0 +1,24 @@
+import { useRef, type RefObject } from 'react';
+import { useMount } from '../../lifecycle/hooks/useMount/useMount.js';
+import { unref, type Unreffable } from '../../utils/unref/unref.js';
+import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
+
+/**
+ * A hook that returns a ref object containing the content rectangle of the target
+ * element. The content rectangle is updated whenever the target element is resized.
+ */
+export function useContentRect(
+ target: Unreffable,
+): RefObject {
+ const contentRectRef = useRef(null);
+
+ useResizeObserver(target, (entries): void => {
+ contentRectRef.current = entries.at(0)?.contentRect ?? null;
+ });
+
+ useMount(() => {
+ contentRectRef.current = unref(target)?.getBoundingClientRect() ?? null;
+ });
+
+ return contentRectRef;
+}
diff --git a/src/hooks/useContentRectState/useContentRectState.mdx b/src/hooks/useContentRectState/useContentRectState.mdx
new file mode 100644
index 0000000..c6566c3
--- /dev/null
+++ b/src/hooks/useContentRectState/useContentRectState.mdx
@@ -0,0 +1,43 @@
+import { Meta } from '@storybook/blocks';
+
+
+
+# useContentRectState
+
+The `useContentRectState` hook is used to get the size of an HTML element, the `DOMRect` is stored
+in a state variable. It uses the `ResizeObserver` API to observe the element `DOMRect`, the target
+is disconnected when the component unmounts.
+
+## Reference
+
+```ts
+function useContentRectState(ref: Unreffable): RefObject;
+```
+
+## Using `useContentRectState` with a RefObject
+
+In this example, the `useRef` hook is used to create a `RefObject` for the `div` element which is
+initially set to null. The `useContentRectState` hook is then called with the ref variable.
+
+```tsx
+function MyComponent() {
+ const ref = useRef(null);
+ const contentRect = useContentRectState(ref);
+
+ return ;
+}
+```
+
+## Using `useContentRectState` with an element
+
+In this example, the `useState` hook is used to create a state variable element which is initially
+set to null. The `useContentRectState` hook is then called with the element variable.
+
+```tsx
+function MyComponent() {
+ const [element, setElement] = useState(null);
+ const contentRect = useContentRectState(element);
+
+ return ;
+}
+```
diff --git a/src/hooks/useContentRectState/useContentRectState.stories.tsx b/src/hooks/useContentRectState/useContentRectState.stories.tsx
new file mode 100644
index 0000000..9f98e41
--- /dev/null
+++ b/src/hooks/useContentRectState/useContentRectState.stories.tsx
@@ -0,0 +1,60 @@
+/* eslint-disable react/jsx-no-literals, react-hooks/rules-of-hooks */
+import type { Meta, StoryObj } from '@storybook/react';
+import { useEffect, useRef } from 'react';
+import { useContentRectState } from './useContentRectState.js';
+
+const meta = {
+ title: 'Hooks / useContentRectState',
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const UseContentRectState: Story = {
+ render() {
+ const ref = useRef(null);
+ const contentRect = useContentRectState(ref);
+
+ useEffect(() => {
+ const animation = ref.current?.animate(
+ [
+ // keyframes
+ { inlineSize: '300px', blockSize: '300px' },
+ { inlineSize: '500px', blockSize: '500px' },
+ { inlineSize: '300px', blockSize: '300px' },
+ ],
+ {
+ // timing options
+ duration: 2000,
+ iterations: Number.POSITIVE_INFINITY,
+ },
+ );
+
+ return () => animation?.cancel();
+ }, []);
+
+ return (
+ <>
+
Element size:
+
+
+ {JSON.stringify(contentRect, null, 2)}
+
+
+ >
+ );
+ },
+};
diff --git a/src/hooks/useContentRectState/useContentRectState.ts b/src/hooks/useContentRectState/useContentRectState.ts
new file mode 100644
index 0000000..aba3ff5
--- /dev/null
+++ b/src/hooks/useContentRectState/useContentRectState.ts
@@ -0,0 +1,26 @@
+import { useRef, useState } from 'react';
+import { useMount } from '../../index.js';
+import { unref, type Unreffable } from '../../utils/unref/unref.js';
+import { useResizeObserver } from '../useResizeObserver/useResizeObserver.js';
+
+/**
+ * A hook that returns the content rectangle of the target element.
+ * The content rectangle is updated whenever the target element is resized.
+ */
+export function useContentRectState(target: Unreffable): DOMRectReadOnly | null {
+ const [contentRect, setContentRect] = useState(null);
+ const rafRef = useRef(0);
+
+ useResizeObserver(target, (entries) => {
+ cancelAnimationFrame(rafRef.current);
+ rafRef.current = requestAnimationFrame(() => {
+ setContentRect(entries.at(0)?.contentRect ?? null);
+ });
+ });
+
+ useMount(() => {
+ setContentRect(unref(target)?.getBoundingClientRect() ?? null);
+ });
+
+ return contentRect;
+}
diff --git a/src/index.ts b/src/index.ts
index 63b73c4..c2c4918 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,8 @@ export * from './gsap/hooks/useScrollAnimation/useScrollAnimation.js';
export * from './gsap/utils/getAnimation/getAnimation.js';
export * from './hocs/ensuredForwardRef/ensuredForwardRef.js';
export * from './hooks/useClientSideValue/useClientSideValue.js';
+export * from './hooks/useContentRect/useContentRect.js';
+export * from './hooks/useContentRectState/useContentRectState.js';
export * from './hooks/useEventListener/useEventListener.js';
export * from './hooks/useForceRerender/useForceRerender.js';
export * from './hooks/useHasFocus/useHasFocus.js';