Skip to content

useElementVisibility

Tracks whether a DOM element is visible within the viewport (or a specified scroll container). Returns a reactive Observable<boolean> that updates automatically via the IntersectionObserver API.

All option values accept either a plain value or an Observable<T>.

isVisible: false
↓ scroll down
target element
import {
function useRef$<T extends Element = Element>(externalRef?: React.Ref<T> | null): Ref$<T>

Creates an observable element ref. Can be used as a drop-in replacement for useRef, composed with callback refs, or used with forwardRef.

The element is wrapped with opaqueObject to prevent legendapp/state from making DOM properties reactive (deep observation).

@paramexternalRef - Optional. Accepts callback ref, RefObject, or null (forwardRef compatible).

@returnsA callable ref that is also observable via get/peek

@example

// standalone — useRef replacement
const el$ = useRef$<HTMLDivElement>();
return <div ref={el$} />;
// forwardRef compatible
const Component = forwardRef<HTMLDivElement>((props, ref) => {
const el$ = useRef$(ref);
return <div ref={el$} />;
});
// callback ref composition
const myRef = useCallback((node: HTMLDivElement | null) => {
node?.focus();
}, []);
const el$ = useRef$(myRef);
return <div ref={el$} />;

useRef$
,
type Ref$<T> = ((node: T | null) => void) & {
get(): OpaqueObject<T> | null;
peek(): OpaqueObject<T> | null;
}
Ref$
,
import useElementVisibility
useElementVisibility
} from "@usels/core";
function
function Component(): React.JSX.Element
Component
() {
const
const el$: Ref$<HTMLDivElement>
el$
=
useRef$<HTMLDivElement>(externalRef?: React.Ref<HTMLDivElement> | undefined): Ref$<HTMLDivElement>

Creates an observable element ref. Can be used as a drop-in replacement for useRef, composed with callback refs, or used with forwardRef.

The element is wrapped with opaqueObject to prevent legendapp/state from making DOM properties reactive (deep observation).

@paramexternalRef - Optional. Accepts callback ref, RefObject, or null (forwardRef compatible).

@returnsA callable ref that is also observable via get/peek

@example

// standalone — useRef replacement
const el$ = useRef$<HTMLDivElement>();
return <div ref={el$} />;
// forwardRef compatible
const Component = forwardRef<HTMLDivElement>((props, ref) => {
const el$ = useRef$(ref);
return <div ref={el$} />;
});
// callback ref composition
const myRef = useCallback((node: HTMLDivElement | null) => {
node?.focus();
}, []);
const el$ = useRef$(myRef);
return <div ref={el$} />;

useRef$
<
interface HTMLDivElement
HTMLDivElement
>();
const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
);
return <
React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
React.RefAttributes<HTMLDivElement>.ref?: React.Ref<HTMLDivElement> | undefined

Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref).

ref
={
const el$: Ref$<HTMLDivElement>
el$
} />;
}
const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
, {
initialValue: boolean
initialValue
: true });

Use once: true to automatically stop observing after the element becomes visible for the first time:

const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
, {
once: boolean
once
: true });

Pass a scrollTarget to observe intersection within a scrollable container instead of the viewport:

const
const container$: Ref$<HTMLDivElement>
container$
=
useRef$<HTMLDivElement>(externalRef?: React.Ref<HTMLDivElement> | undefined): Ref$<HTMLDivElement>

Creates an observable element ref. Can be used as a drop-in replacement for useRef, composed with callback refs, or used with forwardRef.

The element is wrapped with opaqueObject to prevent legendapp/state from making DOM properties reactive (deep observation).

@paramexternalRef - Optional. Accepts callback ref, RefObject, or null (forwardRef compatible).

@returnsA callable ref that is also observable via get/peek

@example

// standalone — useRef replacement
const el$ = useRef$<HTMLDivElement>();
return <div ref={el$} />;
// forwardRef compatible
const Component = forwardRef<HTMLDivElement>((props, ref) => {
const el$ = useRef$(ref);
return <div ref={el$} />;
});
// callback ref composition
const myRef = useCallback((node: HTMLDivElement | null) => {
node?.focus();
}, []);
const el$ = useRef$(myRef);
return <div ref={el$} />;

useRef$
<
interface HTMLDivElement
HTMLDivElement
>();
const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
, {
scrollTarget: Ref$<HTMLDivElement>
scrollTarget
:
const container$: Ref$<HTMLDivElement>
container$
});
const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
, {
threshold: number
threshold
: 0.5,
rootMargin: string
rootMargin
: "0px 0px -100px 0px",
});

All options accept Observable<T> for reactive control:

const
const threshold$: any
threshold$
=
observable<number | {}>(value: Promise<number | {}> | (() => number | {}) | (number | {})): any (+2 overloads)
observable
<number | number[]>(0.5);
const
const rootMargin$: any
rootMargin$
=
observable<unknown>(value: Promise<unknown> | (() => unknown) | unknown): any (+2 overloads)
observable
("0px");
const
const once$: any
once$
=
observable<unknown>(value: Promise<unknown> | (() => unknown) | unknown): any (+2 overloads)
observable
(false);
const
const isVisible$: any
isVisible$
=
import useElementVisibility
useElementVisibility
(
const el$: Ref$<HTMLDivElement>
el$
, {
threshold: any
threshold
:
const threshold$: any
threshold$
,
rootMargin: any
rootMargin
:
const rootMargin$: any
rootMargin$
,
once: any
once
:
const once$: any
once$
,
});
// later — update reactively
const threshold$: any
threshold$
.
any
set
(0.75);
const rootMargin$: any
rootMargin$
.
any
set
("-50px 0px");
export interface UseElementVisibilityOptions {
initialValue?: boolean;
scrollTarget?: MaybeElement;
rootMargin?: string;
threshold?: number | number[];
once?: boolean;
}
export declare function useElementVisibility(element: MaybeElement, options?: DeepMaybeObservable<UseElementVisibilityOptions>): Observable<boolean>;

View on GitHub

  • tigerwest
  • a7392ab 2026-03-06 - feat(core,browser): add sync strategy hooks (tigerwest)