ScreenshotNeo

BlogHow-to

Get an Element’s Bounding Box with Puppeteer

Use Puppeteer’s `ElementHandle.boundingBox()` to read an element’s bounds, understand its coordinate basis, and handle a `null` result safely.

By the ScreenshotNeo team4 October 20266 min read

Call await elementHandle.boundingBox(). It returns an object with x, y, width, and height, or null if the element is not part of the layout. Puppeteer documents the returned box as relative to the main frame. Always check for both a missing element handle and a null box before using the coordinates.

Get the bounding box

Wait for the target selector, call boundingBox() on its ElementHandle, and branch on the nullable result:

const element = await page.waitForSelector('.target');
if (!element) {
  throw new Error('Target element was not found');
}

const box = await element.boundingBox();
if (!box) {
  throw new Error('Target element has no layout box');
}

console.log({
  x: box.x,
  y: box.y,
  width: box.width,
  height: box.height,
});

The method signature is boundingBox(): Promise<BoundingBox | null>. The Puppeteer API reference defines the result as a box relative to the main frame. A non-null box provides the geometry; the method does not itself capture a screenshot or guarantee that the element will remain in the same position after the call.

What the coordinates mean

Puppeteer documents the box as relative to the main frame. Do not silently treat these values as document coordinates or assume they are measured from the page’s document origin. If your next step is a mouse action, Puppeteer documents page mouse coordinates as main-frame CSS pixels relative to the viewport’s upper-left corner. Use the coordinate convention documented for that operation, and re-read the box if the page can move or change between measurement and interaction.

Property Meaning for your code
x, y The box position in the coordinate basis documented by Puppeteer: relative to the main frame.
width, height The box dimensions. Check that both are usable for the operation you intend before dividing, clipping, or scaling.
null No layout box is available for the element. Do not read coordinates from it.

For mouse interaction, use the viewport-relative mouse convention described in the Puppeteer mouse reference. A page may scroll, animate, reflow, or replace the node after measurement, so coordinates can become stale. If the action depends on current geometry, measure immediately before acting and handle a failed interaction as a possible page change.

Why boundingBox() returns null

A null result is a documented outcome: the element is not part of the layout. Puppeteer’s API reference gives an element styled with display: none as an example. This differs from waitForSelector() returning no handle: in the latter case, the selector did not produce a handle within the wait behavior; in the former, you have a handle, but it has no layout box.

  • Check whether the selector matched the intended element rather than a hidden duplicate.
  • Inspect whether the element is conditionally rendered or styled with display: none.
  • If the page changes state after loading, wait for the application state that reveals the target, then measure again.
  • Keep the null check even after waiting: waiting for a selector and having a layout box are separate conditions.

Do not confuse retrieving a box with making an element visible. Puppeteer’s separate ElementHandle.screenshot() guide says that screenshot operation tries to scroll an element into view by default if it is hidden. That behavior is specific to the screenshot method; it does not change the nullable contract of boundingBox().

Use the box for a mouse action

When you need to click the center of an element, derive the point from the box and pass it to the page mouse. This example assumes the box coordinates are appropriate for the documented mouse coordinate space and that the page has not shifted since measurement:

const element = await page.waitForSelector('.target');
if (!element) throw new Error('Target element was not found');

const box = await element.boundingBox();
if (!box) throw new Error('Target element has no layout box');

const x = box.x + box.width / 2;
const y = box.y + box.height / 2;
await page.mouse.click(x, y);

For pages with scrolling, animation, sticky elements, or frequent layout updates, the page can change between measuring and clicking. Reacquire and measure near the interaction, and prefer a locator or element-level action when your goal is to act on the element itself rather than inspect coordinates.

Bounding box or element screenshot?

Choose boundingBox() when you need geometry for positioning, hit testing, layout inspection, or a coordinate-based operation. Choose ElementHandle.screenshot() when the goal is an image of the element. Puppeteer documents the latter separately, including its attempt to scroll a hidden element into view by default. A screenshot does not replace checking a nullable bounding box when your code needs dimensions or coordinates.

Troubleshooting

Symptom Likely cause What to do
waitForSelector() returns no handle The selector did not match during the wait, or the page has not reached the state that renders the target. Check the selector and wait for the page state that creates the element. Handle the missing-handle case before calling boundingBox().
boundingBox() returns null The element is not part of layout; Puppeteer specifically documents display: none as an example. Check visibility state and whether you selected a hidden duplicate. Wait for the state that puts the intended element in layout, then call again.
The click lands in the wrong place The page may have scrolled, animated, reflowed, or replaced the element after measurement, or the coordinates were interpreted in a different space. Measure immediately before the action and follow the documented main-frame CSS pixel, viewport-origin convention for the page mouse.
Code errors while reading box.x The nullable result was used without checking it. Branch on if (!box) before reading properties or computing a point.
The box is zero-sized or unsuitable for a downstream operation The returned geometry may not satisfy the needs of your calculation or capture, even when a value exists. Validate width and height for your use case; do not divide by zero or assume every non-null box is useful for every operation.

Performance, reliability, and cost

boundingBox() is a browser-side geometry query that returns a small object; the Puppeteer documentation does not provide a benchmark or fixed execution time. Avoid polling it in a tight loop when an application-state wait or a bounded retry is enough. For repeated measurements, keep the target stable where possible and reacquire the handle if the page replaces the underlying element.

Reliability comes from handling both asynchronous boundaries: the selector may not yield a handle, and a valid handle may still have no layout box. Coordinate-based follow-up work adds another boundary because layout can change after measurement. Puppeteer itself is a browser automation library; the cited API does not state a per-call service price. If you run a browser yourself, account for the compute and maintenance of that browser environment separately.

Or skip the browser setup

If your goal is a page screenshot rather than inspecting an element’s geometry, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call API can return an image or PDF; it does not return Puppeteer element bounding boxes.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

See the ScreenshotNeo API documentation for the request options and response details. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

What does boundingBox() return?

A promise that resolves to a bounding box or null. The box has position and dimensions and is documented relative to the main frame.

Does it return document coordinates?

The cited method reference says the box is relative to the main frame. It does not establish that these are document/page-scroll coordinates, so do not label them that way.

Does getting a bounding box scroll the element into view?

The cited bounding-box reference does not describe that behavior. Puppeteer’s element screenshot guide separately says the screenshot operation tries to scroll a hidden element into view by default.

Can ScreenshotNeo give me an element bounding box?

No. ScreenshotNeo’s API is for page screenshots and PDFs; use Puppeteer’s boundingBox() when you need element geometry.

Sources