diff --git a/packages/react-dom/src/FadeIn.spec.tsx b/packages/react-dom/src/FadeIn.spec.tsx
new file mode 100644
index 000000000..0ed9ce558
--- /dev/null
+++ b/packages/react-dom/src/FadeIn.spec.tsx
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/react'
+import { FadeIn } from './FadeIn'
+import { mockAllIsIntersecting } from './test-utils'
+
+describe('
', () => {
+ it('renders children with correct styles when in view', () => {
+ mockAllIsIntersecting(false)
+ const mockChild = vi.fn()
+ render(
+
+ {(fadeIn) => (
+
+ {mockChild(fadeIn.style)}
+
+ )}
+
+ )
+ expect(mockChild).toHaveBeenCalledWith({ opacity: 0, willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ mockAllIsIntersecting(true)
+ const child = screen.getByTestId('fade-in-child')
+ expect(child).toHaveStyle({ opacity: '1', willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ expect(mockChild).toHaveBeenCalledWith({ opacity: 1, willChange: 'opacity', transition: 'opacity 300ms ease-in' })
+ })
+})
diff --git a/packages/react-dom/src/FadeIn.test-d.tsx b/packages/react-dom/src/FadeIn.test-d.tsx
new file mode 100644
index 000000000..fc30a2927
--- /dev/null
+++ b/packages/react-dom/src/FadeIn.test-d.tsx
@@ -0,0 +1,10 @@
+import type { ComponentProps } from 'react'
+import type { FadeIn } from './FadeIn'
+
+describe('
', () => {
+ it('type check', () => {
+ expectTypeOf
>().toEqualTypeOf<
+ 'root' | 'rootMargin' | 'threshold' | 'triggerOnce' | 'delay' | 'children' | 'duration' | 'timingFunction'
+ >()
+ })
+})
diff --git a/packages/react-dom/src/FadeIn.tsx b/packages/react-dom/src/FadeIn.tsx
new file mode 100644
index 000000000..8a2ddb6dd
--- /dev/null
+++ b/packages/react-dom/src/FadeIn.tsx
@@ -0,0 +1,22 @@
+import { type ReactNode } from 'react'
+import { type FadeInOptions, useFadeIn } from './useFadeIn'
+
+type FadeInProps = FadeInOptions & {
+ children: (fadeInResult: ReturnType) => ReactNode
+}
+/**
+ * A component that fades in when it comes into view.
+ */
+export function FadeIn({
+ duration = 200,
+ timingFunction = 'linear',
+ delay,
+ root,
+ rootMargin,
+ threshold,
+ triggerOnce,
+ children,
+}: FadeInProps) {
+ const result = useFadeIn({ delay, duration, root, rootMargin, threshold, timingFunction, triggerOnce })
+ return <>{children(result)}>
+}
diff --git a/packages/react-dom/src/index.ts b/packages/react-dom/src/index.ts
index 4ceebec90..e6b6f0a6f 100644
--- a/packages/react-dom/src/index.ts
+++ b/packages/react-dom/src/index.ts
@@ -1,2 +1,4 @@
export { InView } from './InView'
export { useInView } from './useInView'
+export { FadeIn } from './FadeIn'
+export { useFadeIn } from './useFadeIn'
diff --git a/packages/react-dom/src/useFadeIn.ts b/packages/react-dom/src/useFadeIn.ts
new file mode 100644
index 000000000..f817e4f7c
--- /dev/null
+++ b/packages/react-dom/src/useFadeIn.ts
@@ -0,0 +1,48 @@
+import { type CSSProperties, useMemo } from 'react'
+import { type InViewOptions, useInView } from './useInView'
+import type { OmitKeyof } from './utility-types'
+
+export type FadeInOptions = OmitKeyof<
+ InViewOptions,
+ 'fallbackInView' | 'initialInView' | 'skip' | 'onChange' | 'trackVisibility'
+> & {
+ /**
+ * The duration in milliseconds of the animation.
+ * @default 200
+ */
+ duration?: number
+ /**
+ * The timing function of the animation.
+ * @default 'linear'
+ */
+ timingFunction?: CSSProperties['transitionTimingFunction']
+}
+type FadeInResult = Pick, 'ref'> & {
+ style: {
+ opacity: 0 | 1
+ willChange: 'opacity'
+ transition: `opacity ${number}ms ${Required['transitionTimingFunction']}`
+ }
+}
+export function useFadeIn({
+ duration = 200,
+ timingFunction = 'linear',
+ delay,
+ root,
+ rootMargin,
+ threshold,
+ triggerOnce,
+}: FadeInOptions): FadeInResult {
+ const { inView, ref } = useInView({ delay, root, rootMargin, threshold, triggerOnce })
+ return useMemo(
+ () => ({
+ ref,
+ style: {
+ opacity: inView ? 1 : 0,
+ willChange: 'opacity',
+ transition: `opacity ${duration}ms ${timingFunction}` as const,
+ },
+ }),
+ [inView, duration, timingFunction]
+ )
+}
diff --git a/packages/react-dom/src/utility-types/OmitKeyof.test-d.ts b/packages/react-dom/src/utility-types/OmitKeyof.test-d.ts
new file mode 100644
index 000000000..1391673bb
--- /dev/null
+++ b/packages/react-dom/src/utility-types/OmitKeyof.test-d.ts
@@ -0,0 +1,176 @@
+import type { OmitKeyof } from './OmitKeyof'
+
+describe('OmitKeyof', () => {
+ it("'s string key type check", () => {
+ type A = {
+ x: string
+ y: number
+ }
+
+ type ExpectedType = {
+ x: string
+ }
+
+ // Bad point
+ // 1. original Omit can use 'z' as type parameter with no type error
+ // 2. original Omit have no auto complete for 2nd type parameter
+ expectTypeOf>().toEqualTypeOf()
+
+ // Solution
+
+ // 1. strictly
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 'z' as type parameter with type error because A don't have key 'z'
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 'z' | 'y'
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 'z' as type parameter with type error because A don't have key 'z'
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 'z' | 'y',
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
+ 'strictly'
+ >
+ >().toEqualTypeOf()
+
+ // 2. safely
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 'z' as type parameter type error with strictly parameter or default parameter
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 'z' | 'y'
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // With 'safely', OmitKeyof can use 'z' as type parameter like original Omit but This support autocomplete too yet for DX.
+ 'z' | 'y',
+ 'safely'
+ >
+ >().toEqualTypeOf()
+ })
+
+ it("'s number key type check", () => {
+ type A = {
+ [1]: string
+ [2]: number
+ }
+
+ type ExpectedType = {
+ [1]: string
+ }
+
+ // Bad point
+ // 1. original Omit can use 3 as type parameter with no type error
+ // 2. original Omit have no auto complete for 2nd type parameter
+ expectTypeOf>().toEqualTypeOf()
+
+ // Solution
+
+ // 1. strictly
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 3 as type parameter with type error because A don't have key 3
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 3 | 2
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 3 as type parameter with type error because A don't have key 3
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 3 | 2,
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
+ 'strictly'
+ >
+ >().toEqualTypeOf()
+
+ // 2. safely
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use 3 as type parameter type error with strictly parameter or default parameter
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ 3 | 2
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // With 'safely', OmitKeyof can use 3 as type parameter like original Omit but This support autocomplete too yet for DX.
+ 3 | 2,
+ 'safely'
+ >
+ >().toEqualTypeOf()
+ })
+
+ it("'s symbol key type check", () => {
+ const symbol1 = Symbol()
+ const symbol2 = Symbol()
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const symbol3 = Symbol()
+
+ type A = {
+ [symbol1]: string
+ [symbol2]: number
+ }
+
+ type ExpectedType = {
+ [symbol1]: string
+ }
+
+ // Bad point
+ // 1. original Omit can use symbol3 as type parameter with no type error
+ // 2. original Omit have no auto complete for 2nd type parameter
+ expectTypeOf>().toEqualTypeOf()
+
+ // Solution
+
+ // 1. strictly
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use symbol3 as type parameter with type error because A don't have key symbol3
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ typeof symbol3 | typeof symbol2
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use symbol3 as type parameter with type error because A don't have key symbol3
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ typeof symbol3 | typeof symbol2,
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
+ 'strictly'
+ >
+ >().toEqualTypeOf()
+
+ // 2. safely
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // OmitKeyof can't use symbol3 as type parameter type error with strictly parameter or default parameter
+ // @ts-expect-error Type does not satisfy the constraint keyof A
+ typeof symbol3 | typeof symbol2
+ >
+ >().toEqualTypeOf()
+ expectTypeOf<
+ OmitKeyof<
+ A,
+ // With 'safely', OmitKeyof can use symbol3 as type parameter like original Omit but This support autocomplete too yet for DX.
+ typeof symbol3 | typeof symbol2,
+ 'safely'
+ >
+ >().toEqualTypeOf()
+ })
+})
diff --git a/packages/react-dom/src/utility-types/OmitKeyof.ts b/packages/react-dom/src/utility-types/OmitKeyof.ts
new file mode 100644
index 000000000..69f6be78c
--- /dev/null
+++ b/packages/react-dom/src/utility-types/OmitKeyof.ts
@@ -0,0 +1,11 @@
+export type OmitKeyof<
+ TObject,
+ TKey extends TStrictly extends 'safely'
+ ?
+ | keyof TObject
+ | (string & Record)
+ | (number & Record)
+ | (symbol & Record)
+ : keyof TObject,
+ TStrictly extends 'strictly' | 'safely' = 'strictly',
+> = Omit
diff --git a/packages/react-dom/src/utility-types/index.ts b/packages/react-dom/src/utility-types/index.ts
new file mode 100644
index 000000000..e439d3d4c
--- /dev/null
+++ b/packages/react-dom/src/utility-types/index.ts
@@ -0,0 +1 @@
+export type { OmitKeyof } from './OmitKeyof'