Skip to content

Playwright Mouse Cursor

Inject a visible mouse cursor into Playwright pages. Real mouse cursors don't appear in screenshots or screencasts, so this overlays a fake cursor image that you can position anywhere on the page.

When to use

Use this when you need a visible cursor in Playwright — for screenshots, screen recordings, demos, or any automation where showing cursor position matters.

The pattern

Cursor helper functions

Inject a fake cursor as a fixed-position image overlay. The element uses pointer-events: none so it never interferes with clicks, hovers, or other interactions:

javascript
import { readFileSync } from "node:fs";

function cursorDataUri(filePath) {
  const buffer = readFileSync(filePath);
  const base64 = buffer.toString("base64");
  return `data:image/png;base64,${base64}`;
}

async function showCursor(page, { src, x, y, width, height }) {
  await page.evaluate(
    ({ src, x, y, width, height }) => {
      let el = document.getElementById("fake-cursor");
      if (!el) {
        el = document.createElement("img");
        el.id = "fake-cursor";
        el.style.cssText = `
          position: fixed;
          pointer-events: none;
          z-index: 999999;
        `;
        document.body.appendChild(el);
      }
      el.src = src;
      el.style.left = `${x}px`;
      el.style.top = `${y}px`;
      el.style.width = `${width}px`;
      el.style.height = `${height}px`;
      el.style.display = "block";
    },
    { src, x, y, width, height },
  );
}

async function hideCursor(page) {
  await page.evaluate(() => {
    const el = document.getElementById("fake-cursor");
    if (el) el.style.display = "none";
  });
}

Retina cursor images

For 2x retina displays, use a cursor image at double the pixel density and render it at half its native size in CSS. A 64×64 px image rendered at 32×32 CSS px appears crisp on a 2x screenshot:

javascript
const arrowUri = cursorDataUri("cursors/cursor-default.2x.png");
await showCursor(page, {
  src: arrowUri,
  x: 700,
  y: 500,
  width: 32,
  height: 32,
});

Positioning on an element

Center the cursor over a specific element using its bounding box. Offset by half the cursor size so the tip lands at the element's center:

javascript
const box = await page.locator("button.my-button").boundingBox();
await showCursor(page, {
  src: pointerUri,
  x: box.x + box.width / 2 - 16,
  y: box.y + box.height / 2 - 16,
  width: 32,
  height: 32,
});

Hiding the cursor

Hide the cursor when you don't want it visible — for example, between screenshots that shouldn't show a cursor:

javascript
await hideCursor(page);

Because the overlay uses pointer-events: none, you don't need to hide it before performing interactions. It only needs to be hidden when you don't want it to appear in the output.

Trade-offs

ChoiceProCon
pointer-events: noneNo interference with pageCan't show click state
Fixed positioningStays put during scrollDoesn't scroll w/page
Data URI sourceNo network request neededLarger inline payload
2x cursor imagesCrisp on retina displaysOverkill for 1x