How to Use the EyeDropper API in Browsers
For about twenty years, "color picker on the web" meant "color picker that can only see colors inside its own window." If you wanted to sample the brand red on a competitor's landing page, the only options were the browser's developer-tools eyedropper (one extra panel and four clicks) or a screenshot-plus-Photoshop loop (a minute of context switching). The EyeDropper API, which landed in Chrome 95 in October 2021 and is now in every Chromium-based browser, removed that limitation: a web page can now ask the user "pick any color on your screen" and get back a single sRGB hex string. This guide explains how to call it, what the user sees, how to gracefully handle the browsers that haven't shipped it, and the privacy story that lets it exist at all.
What the API actually exposes
The whole interface is one constructor and one method. There is no event listener, no observer, no async setup. From the W3C draft spec:
const eyeDropper = new EyeDropper();
const { sRGBHex } = await eyeDropper.open();
// sRGBHex is a 7-character string like "#3b82f6"
open() returns a promise that resolves once the user clicks a pixel, or rejects if the user cancels (Esc, or clicking outside the page in some implementations). That's it. There's no way to read a color without user interaction — the API is gated behind an opening gesture and a confirming pick, which is what makes it acceptable from a privacy standpoint (more on that below).
The result has a single field, sRGBHex. It's always seven characters (leading # plus six hex digits) and always in sRGB color space regardless of the user's monitor profile. If you need RGB integers, parse it the obvious way; the companion color picker does this for you and shows the other formats live.
Feature detection, because half the world is on Safari
As of mid-2026, EyeDropper ships on:
- Chrome 95+ (Oct 2021)
- Edge 95+ (Oct 2021)
- Opera 81+ (Nov 2021)
- All Chromium-based browsers that track upstream (Brave, Vivaldi, Arc)
It does not ship on:
- Firefox — tracked as bug 1769341, no shipping date
- Safari — no public WebKit position
- Any mobile browser — including mobile Chrome and Safari iOS, which have technical reasons (no system-wide screen capture surface)
The detection is one line:
const supported = typeof window !== 'undefined' && 'EyeDropper' in window;
Run this after hydration in a React or Next.js app — server-side, window is undefined and you'll get a hydration mismatch error. The clean pattern is:
const [supported, setSupported] = useState(false);
useEffect(() => {
setSupported(typeof window !== 'undefined' && 'EyeDropper' in window);
}, []);
On unsupported browsers, hide the button entirely or replace it with a short explanation. Don't disable a visible button — users hate dead UI. The fallback that costs nothing is the native <input type="color"> element, which every browser has shipped since 2014 and which opens a color wheel anchored to whatever element you attach it to. Wire it as a sr-only overlay on your preview swatch and clicking the swatch will trigger the system color picker — not quite "anywhere on screen" but still useful.
Handling user cancellation
open() rejects with an exception (not a typed error) when the user cancels — pressing Esc or clicking outside the dropper region in browsers that allow it. Treat this as a no-op, not an error:
async function pickColor() {
if (!('EyeDropper' in window)) return;
try {
const { sRGBHex } = await new EyeDropper().open();
setColor(sRGBHex);
} catch {
// User cancelled — no toast, no state change.
}
}
Showing an error message when the user cancels is the worst UX possible: the cancellation was the user actively saying "never mind," and explaining to them that "you cancelled" is condescending. Silent ignore matches the behavior of every native color picker.
The security and privacy model
The API exists in a privacy-conscious form for three reasons:
1. It requires a user gesture. new EyeDropper().open() only resolves if called from a user-initiated event (click, keyboard). A script can't loop on open() and read the screen unattended.
2. Each call returns exactly one pixel. There's no "give me a screenshot" mode, no rectangle-selection mode, no continuous-read mode. The user clicks one pixel, the API returns one color, and the session ends. To sample N colors you make N user gestures.
3. The user sees the system-wide eyedropper cursor. When open() is called, the browser shows a special cursor and (in some implementations) a zoom inset. The user always knows the eyedropper is active because their cursor changed. There's no way to make it silent.
The original WICG proposal also forbade reading colors from cross-origin iframes you don't own. In practice this restriction was relaxed — the browser shows the color of whatever the user clicked, regardless of which surface (your tab, another tab, your wallpaper). The privacy argument is that any one specific color from one specific pixel on your screen is not a useful exfiltration channel — bandwidth is effectively one HEX per user gesture, and the user explicitly authorized the read by clicking.
You can read more on the security model in the WICG EyeDropper API explainer.
The optional signal parameter
open() accepts an options bag with an AbortSignal. If you want to programmatically cancel a pending pick (e.g. the user navigated away from your modal), pass a signal:
const controller = new AbortController();
modal.addEventListener('close', () => controller.abort(), { once: true });
try {
const result = await new EyeDropper().open({ signal: controller.signal });
setColor(result.sRGBHex);
} catch (e) {
if (e.name === 'AbortError') return; // expected on modal close
// other errors: user cancel via Esc / click — also silent ignore
}
This is the only argument the API takes. There's no styling, no positioning, no preset list, no anchor element.
What the API doesn't do
- Doesn't return a region. You get one pixel per call. To average over a 5×5 region you'd need 25 calls (impractical) or screen-capture-then-process (use
getDisplayMedia()). - Doesn't return brush samples. The system cursor isn't a brush — it's a 1×1 sampler.
- Doesn't respect display color profile. You always get sRGB. If the user's monitor is in P3 wide-gamut mode, the browser color-converts to sRGB before returning. For wide-gamut design work, the API is the wrong tool.
- Doesn't work over WebRTC streams in a useful way — the API reads what the OS compositor presents, not what's in a
<video>element. To sample a video pixel, draw it to canvas first. - Doesn't tell you which surface the pixel came from. You can't distinguish "user clicked my tab" from "user clicked their desktop wallpaper."
When EyeDropper is the wrong tool
If you need to color-grade an image, use <input type="file"> + canvas. The EyeDropper API doesn't see uploaded files; it sees the screen.
If you need to extract dominant colors from a photo, use a library like Vibrant.js or ColorThief on a canvas. EyeDropper is interactive, not analytical.
If you need real-time color tracking (e.g. for ARkit-style applications), you need camera permission + computer vision, not EyeDropper.
But for the 90% case — "let me grab this brand color from a live web page and paste it into my CSS" — EyeDropper plus a four-format converter is the fastest workflow on the web today. The online color picker on ScreenTools bundles both, with the unsupported-browser fallback already wired and a saved-palette that persists in localStorage.
Putting it together
A minimal working integration in 25 lines of React:
import { useEffect, useState } from 'react';
export function EyedropperButton({ onPick }) {
const [supported, setSupported] = useState(false);
useEffect(() => {
setSupported('EyeDropper' in window);
}, []);
if (!supported) {
return <p>Use Chrome, Edge, or Opera on desktop to pick colors from screen.</p>;
}
async function pick() {
try {
const { sRGBHex } = await new window.EyeDropper().open();
onPick(sRGBHex);
} catch {
// cancelled — no-op
}
}
return <button onClick={pick}>Pick a color</button>;
}
That's the whole API. One constructor, one method, one promise, one return field. The hardest part is remembering to feature-detect after hydration; everything else is web platform plumbing that the browser already provides.
Related Articles
The 7 Best PPI / DPI Calculator Tools Compared (2026)
We tested seven free PPI calculators across 12 device profiles — phone, laptop, desktop monitor, TV — and ranked them by accuracy, speed, and what you can do with the result. Here's what to use for what.
The Complete Guide to the Online Color Picker
Pick any pixel on screen, convert HEX ↔ RGB ↔ HSL ↔ HSV, save a palette, and share URLs — everything an online color picker can do, plus how its math actually works.
The Complete Guide to HEX, RGB, HSL & HSV Color Conversion
Convert between HEX, RGB, HSL, and HSV — why all four formats exist, when to use each one, exactly how the math works, and how to convert a whole list at once instead of one color at a time.