Skip to content

Capabilities · API

EyeDropper API

Published

The EyeDropper API gives a page the browser’s own eyedropper tool: new EyeDropper().open() suppresses input to the page, lets the user pick one pixel anywhere on screen, including outside the browser window, and resolves with that pixel’s colour as a #rrggbb string. It exists so custom colour pickers do not have to screenshot the page to sample a colour.

Chrome 95 and Edge 95 ship it on desktop only; BCD records version_added: false for Chrome on Android and Android WebView. Firefox and Safari have no implementation (BCD api.EyeDropper). Chrome 96 moved the interface behind [SecureContext], so on http:// origins other than localhost window.EyeDropper is undefined.

const eyeDropper = new EyeDropper();
eyeDropper.open()
eyeDropper.open(options)

The constructor takes no arguments and does nothing visible. open() returns a Promise<ColorSelectionResult>; the result’s only member is sRGBHex, a DOMString holding a valid simple colour such as "#3366ff". While the promise is pending the page is in “eyedropper mode” and receives no UI events. open() must be called with transient user activation, so it belongs inside a click or keydown handler.

open() takes one optional options argument, a ColorSelectionOptions dictionary with a single member.

Member Type Required Description
signal AbortSignal No Aborting the signal exits eyedropper mode, dismisses the browser UI, and rejects the pending promise with the signal’s abort reason (an AbortError DOMException unless abort() was given another reason).

The resolved ColorSelectionResult dictionary has one member, sRGBHex, a six-digit #rrggbb string. The specification exposes no alpha channel, so a translucent pixel is reported as the colour composited on screen.

open() rejects its promise with one of the following. The first two are checked synchronously before any UI appears.

Exception Condition
NotAllowedError The window has no transient user activation.
InvalidStateError Another eyedropper is already open in this window.
AbortError The user cancelled the selection (Escape in Chrome) before picking a pixel.
OperationError The browser could not present its UI or could not read screen content; Chrome also uses it when the feature is disabled by policy.
The signal’s abort reason options.signal was already aborted when open() was called, or was aborted while the promise was pending.

Both examples test "EyeDropper" in window before showing any pick button and keep an <input type="color"> in the page as the path for Firefox, Safari, and every mobile browser.

Picking a colour with a native colour input as the fallback

Section titled “Picking a colour with a native colour input as the fallback”

When the constructor is missing, hide the pick button and leave the colour input visible; the input gives every browser a working colour picker, just not one that can sample pixels outside the page.

const pickButton = document.querySelector("#pick");
const colorInput = document.querySelector("#color");
if (!("EyeDropper" in window)) {
pickButton.hidden = true;
colorInput.hidden = false;
} else {
pickButton.addEventListener("click", async () => {
const eyeDropper = new EyeDropper();
try {
const { sRGBHex } = await eyeDropper.open();
colorInput.value = sRGBHex;
} catch (err) {
if (err.name !== "AbortError") console.error(`${err.name}: ${err.message}`);
}
});
}

AbortError is the normal outcome when the user presses Escape, so the handler ignores it and surfaces only the other names.

Cancelling a pick from a timeout or a second button

Section titled “Cancelling a pick from a timeout or a second button”

Pass an AbortSignal when a pick should not stay open indefinitely. AbortSignal.timeout() rejects with a TimeoutError reason, which the catch branch distinguishes from the user’s own cancel.

async function pickWithTimeout(ms) {
if (!("EyeDropper" in window)) return null;
const eyeDropper = new EyeDropper();
try {
const { sRGBHex } = await eyeDropper.open({ signal: AbortSignal.timeout(ms) });
return sRGBHex;
} catch (err) {
if (err.name === "TimeoutError") console.info("Eyedropper closed after timeout");
return null;
}
}
document.querySelector("#pick").addEventListener("click", async () => {
const hex = await pickWithTimeout(10_000);
if (hex) document.documentElement.style.setProperty("--accent", hex);
});

A null return covers unsupported browsers, timeouts, and user cancellation alike, so the caller needs one branch rather than three.

Specifications

SpecificationStatus
EyeDropper APIWICG draft