LOKE Design System
Hooks

useSize

Observe and return a DOM element’s width and height using ResizeObserver — updates synchronously via a layout effect and returns undefined until an element is available.

useSize

useSize measures a DOM element’s width and height using ResizeObserver, returning a { width, height } object that updates whenever the element’s border-box size changes. It uses a layout effect so the first size is provided as early as possible on the client.

  • Returns undefined until an element is available
  • Updates on any size change (content, border, padding)
  • Uses border-box sizing for consistent measurements

Pass a stable element reference (e.g. ref.current or a callback ref state) to useSize. When the element changes, the observer is reattached.


API

function useSize(
  element: HTMLElement | null
): { width: number; height: number } | undefined;

Prop

Type


Usage

1) Basic measurement with a standard ref

import * as React from 'react';
import { useSize } from '@loke/ui/use-size';

export function MeasuredPanel() {
  const ref = React.useRef<HTMLDivElement | null>(null);
  const size = useSize(ref.current); // width/height or undefined

  return (
    <div className="space-y-3">
      <div ref={ref} className="rounded border bg-card p-6">
        Resize your window or change content to see updates.
      </div>
      <div className="text-xs text-muted-foreground">
        {size ? `w: ${size.width}px, h: ${size.height}px` : 'Measuring…'}
      </div>
    </div>
  );
}

2) Using a callback ref (stable across re-renders)

import * as React from 'react';
import { useSize } from '@loke/ui/use-size';

export function CallbackRefExample() {
  const [el, setEl] = React.useState<HTMLDivElement | null>(null);
  const size = useSize(el);

  return (
    <div>
      <div ref={setEl} className="rounded border bg-card p-4">
        Observed via a callback ref.
      </div>
      <pre className="mt-2 text-xs text-muted-foreground">
        {JSON.stringify(size)}
      </pre>
    </div>
  );
}

3) Drive layout from size (e.g., responsive columns)

import * as React from 'react';
import { useSize } from '@loke/ui/use-size';

export function CardGrid() {
  const ref = React.useRef<HTMLDivElement | null>(null);
  const size = useSize(ref.current);
  const colWidth = 280;
  const cols = Math.max(1, Math.floor((size?.width ?? colWidth) / colWidth));

  return (
    <div ref={ref}>
      <div
        className="grid gap-3"
        style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}
      >
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="rounded border bg-card p-4">
            Card {i + 1}
          </div>
        ))}
      </div>
    </div>
  );
}

Behavior & details

  • First measurement:
    • On mount (client), the hook sets an initial { width, height } using offsetWidth/offsetHeight to provide values as early as possible before observer events.
  • Subsequent updates:
    • Uses ResizeObserver with the border-box to keep values accurate across padding and border changes.
  • Cleanup:
    • Automatically disconnects the observer when the element changes or unmounts.

The hook returns undefined when no element is available. Always guard against this when rendering measurements.


SSR and environments

  • This hook uses a layout effect and ResizeObserver, which are browser-only features.
  • Use it in Client Components (e.g., Next.js "use client") or guard usage in non-DOM environments.
  • During SSR, skip rendering UI that depends on the measurement until hydrated, or render a fallback.

Tips

  • Prefer a callback ref (e.g., setEl) if the element itself may change across renders.
  • If you only care about content-box changes, you can adapt the hook (this version returns border-box sizes).
  • Combine with your responsive primitives to simplify layout decisions (e.g., switch between Columns configs based on width).

Import

import { useSize } from '@loke/ui/use-size';