DEV Community

Cover image for Chrome Already Has The Eyedropper You're Building
Parsa Jiravand
Parsa Jiravand

Posted on Originally published at bestpractic.org

Chrome Already Has The Eyedropper You're Building

A designer messages you: "Can we let people pick a color from anywhere on their screen? Like, an eyedropper — click it, then click on my logo, and it grabs that exact blue."

Sure, you say. You've done color pickers before. How hard can this be.

Guess before you scroll: how many lines of code does the real answer take?

The canvas rabbit hole

The obvious plan: capture a snapshot of the page onto a <canvas>, track the mouse, and read the pixel under the cursor with getImageData.

const ctx = canvas.getContext("2d");
canvas.addEventListener("mousemove", (e) => {
  const { data } = ctx.getImageData(e.offsetX, e.offsetY, 1, 1);
  const hex = "#" + [data[0], data[1], data[2]]
    .map((c) => c.toString(16).padStart(2, "0"))
    .join("");
  swatch.style.background = hex;
});
Enter fullscreen mode Exit fullscreen mode

That much works. Then the designer actually tries it, and moves the cursor off the page — onto their open Figma tab, onto the taskbar, onto the desktop wallpaper behind everything — and nothing happens. Your loupe just shows whatever was already under the cursor the last time it was over your canvas.

It's not a bug you can patch. A <canvas> can only ever paint pixels you drew into it. It has no way to see the rest of the screen — no API reads "whatever is currently under the OS cursor," because that would mean a random web page could screenshot your other tabs and your desktop on a whim. The sandbox that keeps that from happening is also the wall your eyedropper just hit.

So now you're looking at getDisplayMedia() — the screen-capture API — just to grab a frame of the whole display, decode it into a video element, draw that onto a canvas, and read pixels from the copy. It works, technically. It also means a permission prompt, a capture-in-progress browser banner, and maybe 150 lines of plumbing for something that, on your OS, is a single click in the system color picker.

The four lines that replace all of it

Chrome — and Edge, and other Chromium browsers — has shipped a real system color picker as a Web API since late 2021: window.EyeDropper. No canvas, no screen capture, no pixel math.

async function pickColor() {
  const eyeDropper = new EyeDropper();
  const { sRGBHex } = await eyeDropper.open();
  swatch.style.background = sRGBHex;
}

pickButton.addEventListener("click", pickColor);
Enter fullscreen mode Exit fullscreen mode

Click the button, the cursor turns into a genuine magnifier loupe, and it works anywhere on the screen — over the page, over another app, over the taskbar. Click a pixel, and open() resolves with { sRGBHex: "#3b82f6" }. That's the whole feature. The OS is doing the screen-reading for you, outside the browser's sandbox entirely, because the browser itself asked the OS to hand back one color, not a frame of pixels your JavaScript could keep.

That "outside the sandbox" part is worth sitting with for a second: this is the one common case where a web page gets to see something beyond its own tab without a permission prompt, a video stream, or a capture banner — because the API is deliberately narrow. It hands back a single hex string and nothing else. There's no way to get a full-screen screenshot out of it, no way to sample a rectangle, no stream to record. That narrowness is the whole reason it doesn't need the heavyweight ceremony getDisplayMedia() does.

Two things that will bite you if you skip them

It has to start from a real user gesture. Call eyeDropper.open() from inside a setTimeout, a fetch().then(), or on page load, and it throws instead of opening — the same "was this really a click" gate that guards requestFullscreen() and autoplaying audio. Wire it to a click listener directly and you're fine; wire it behind an async chain and you'll see a rejected promise with no obvious cause until you check what actually triggered the call.

The reader can bail, and your code has to expect that. Press Escape while the loupe is active, and open() rejects with a DOMException named AbortError — not a resolved value with null, an actual rejection:

async function pickColor() {
  try {
    const eyeDropper = new EyeDropper();
    const { sRGBHex } = await eyeDropper.open();
    swatch.style.background = sRGBHex;
  } catch (err) {
    if (err.name === "AbortError") return; // user pressed Escape — not a bug
    console.error(err);
  }
}
Enter fullscreen mode Exit fullscreen mode

Skip the try/catch and one Escape key press turns your color picker into an uncaught promise rejection in the console, on the most ordinary possible user action.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

Where it doesn't reach yet

Feature-detect before you use any of this — if ("EyeDropper" in window) — because Firefox and Safari don't ship it. There's no polyfill worth using either: the entire point is reading pixels outside the page, and nothing short of the OS itself can do that safely. Fall back to a plain <input type="color"> (which every browser already renders with its own OS color picker, just scoped to that one input) rather than resurrecting the canvas-and-getDisplayMedia() approach as a shim — you'd be rebuilding the exact complexity this API exists to delete.

It also only ever gives you one thing: a hex string. No alpha channel, no color name, no continuous "drag to preview" stream of values while you move — just the single pixel you clicked on, once. For most "pick a color from my design" features, that's the entire ask anyway.

So: the designer's request that sounded like a screen-capture project is four lines and a catch block, on the one browser engine that ships it. The canvas loupe isn't wrong, exactly — it's solving a problem the platform already solved for you, just not the way most people go looking for it.

What's the last browser API you built a workaround for, before finding out it already existed?

🧠 Test yourself

Think it clicked? Take the 7-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.


🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.

Thanks for reading! Let's stay connected:

Top comments (0)