JavaScript EyeDropper Table

PieceWhat it doesField note
new EyeDropper()The picker handlestateless constructor - it holds nothing; make one per session and gate the whole UI on EyeDropper in window
ed.open()The one methodtakes over with a browser-owned magnifier across the entire screen; must run from a user gesture (transient activation) or it will not start
{sRGBHex}The result fieldthe promise resolves once, to one lowercase hex string like #ffa500 - opaque, no alpha, no color-space tag
open({signal})The cancel wireoptional AbortSignal; ctrl.abort() closes the native overlay and rejects the promise - the only programmatic exit
AbortErrorThe normal exitthe user pressed Escape or your code aborted - catch it as a branch of the flow, not an exception in telemetry
Chromium onlyThe platform gateno Firefox, no Safari, secure context required; the constructor line itself throws where unsupported
one pixel, sRGBThe sampling trutha single screen pixel, no region averaging, quantized down to sRGB even on P3 displays, and no coordinates of where the user picked
input type=colorThe fallback laneuniversal native picker - its dropdown carries an eyedropper on several platforms, zero prompts, works in every browser
Reference: the MDN EyeDropper API and EyeDropper.open(). Two lines in total: new EyeDropper(), then const {sRGBHex} = await ed.open() - the browser magnifier hands back one screen pixel as hex.
Bottom line: call open() only from a real click, pass an AbortSignal so a cancel button can actually close the overlay, catch AbortError as a changed-mind branch, and parse the hex once with parseInt. For colors inside your own page, canvas getImageData and getComputedStyle have no prompt and no Chromium boundary.
Related tools: the canvas table (pixel sampling home turf), the clipboard table (copy the hex onward), the permissions table (the gesture and prompt model), the Web Share table (send the palette onward), and the WebGPU table (wide-gamut color where P3 matters).

EyeDropper is the smallest useful API on the platform: it samples one pixel of the screen and hands you the color. The whole dance is two lines - new EyeDropper(), then const {sRGBHex} = await ed.open() - and between those lines the browser takes over: a magnifier overlay follows the pointer across the entire display, the user positions it, clicks, and your promise resolves with a lowercase hex string like #ffa500. No canvas to own, no image to upload, no permissions dialog ladder - just the one gesture that opened it, which is exactly the point: the API was designed so that screen-pixel access only ever happens behind a visible, deliberate user action.

Bottom line: one method, one result field, one cancel wire. open() must be called from a user gesture (the platform requires transient activation, so wiring it to a button click is the correct and only shape); it returns a promise resolving to {sRGBHex: '#rrggbb'} - a single object with a single field, opaque hex, no alpha channel, no coordinates of where on screen the user actually picked. Cancellation is first-class: pass open({signal: controller.signal}) and your own cancel button can abort the picker, rejecting with AbortError - the same rejection the user produces by pressing Escape. Catch AbortError as a normal branch of your UI flow (the user changed their mind), not as an exception to log; that single distinction separates polished picker UIs from console-error archaeology.

The boundaries, stated plainly: Chromium-only - Firefox and Safari do not ship it, so 'EyeDropper' in window is your feature detect and <input type=color> is the honest fallback (several native pickers carry a built-in eyedropper of their own). The sample is one screen pixel, not a region average, and it arrives in sRGB - no Display-P3 wide gamut, which matters if you are matching colors for print or wide-gamut design work. And because the API can read any pixel on the user's screen, browsers treat it as a sensitive surface: gesture-gated, secure-context-only, and trivially rejectable. For colors that already live inside your page, skip it - canvas getImageData or getComputedStyle have no prompt and no gesture tax; EyeDropper earns its keep for everything outside your DOM: design files in another window, a photo in the local viewer, a brand color on any website.

How to use

  1. Wire it to a button and treat dismissal as flow: btn.addEventListener('click', async () => { try { const {sRGBHex} = await new EyeDropper().open(); applyColor(sRGBHex); } catch (err) { if (err.name === 'AbortError') return; /* user dismissed - stay quiet */ throw err; } });
  2. Add a real cancel button with a signal: const ctrl = new AbortController(); const result = ed.open({signal: ctrl.signal}); cancelBtn.onclick = () => ctrl.abort(); - without a signal, Escape is the only way out and your pending promise just waits.
  3. Feature-detect and fall back cleanly: if (!('EyeDropper' in window)) reveal a hidden <input type=color> instead - the native picker ships an eyedropper mode on several platforms, and the input works in every browser ever made.
  4. Parse the hex once into components: const n = parseInt(sRGBHex.slice(1), 16); then r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255 - or template it straight into rgb() / CSS custom properties; sRGBHex is always 7 characters of lowercase hex with no alpha.
  5. Sample inside your own page without the API at all: ctx.getImageData(x, y, 1, 1).data for canvas pixels, getComputedStyle(el).color for rendered styles - no gesture, no prompt, and none of the Chromium-only boundary; reserve EyeDropper for pixels your document does not own.

Frequently asked questions

Why does open() demand a click, and what is the browser actually showing my user?

The gesture requirement (transient activation) is the privacy model, not a formality: EyeDropper can read the color of any pixel on the screen, which includes things the user never gave your page - a document in another window, a photo in a local viewer, a password hint scrawled in a notes app. Browsers close that hole by making screen sampling impossible to trigger from script alone: a real, recent user click must hand off to open(), and what appears is a browser-owned magnifier overlay that follows the pointer across the whole display until the user clicks a pixel or presses Escape. Your page stays in the dark about the journey: no events during picking, no coordinates in the result, just the final hex. The design consequence for your UI: never attempt to call open() outside a handler (it will not work), do not build flows that batch-sample multiple colors without re-clicking (each pick is its own gesture), and do not treat the magnifier as skinnable or brandable - it is the browser vouching for the interaction, which is precisely why users trust it. The same logic explains the secure-context rule: an API that can silently observe screen colors has no business running over plain HTTP.

How do I cancel the picker programmatically - and what is the right way to handle AbortError?

Pass an AbortSignal: const ctrl = new AbortController(); ed.open({signal: ctrl.signal}). Calling ctrl.abort() while the magnifier is up closes the picker and rejects the promise with an AbortError DOMException. This is the difference between a cancel button that works and one that lies: without a signal, your only exits are the user clicking a color or pressing Escape - your in-page cancel button can close its own modal chrome, but the native overlay stays up and the promise stays pending. Handle the rejection as control flow, not failure: AbortError means a human looked at the picker and declined - return quietly, restore whatever button state you parked, and maybe nudge nothing at all. The anti-pattern is dumping it into your global error handler, where a user pressing Escape on a color picker starts looking like an exception storm in your telemetry. One more branch worth distinguishing: if the environment lacks EyeDropper entirely, the constructor line itself throws a TypeError - feature-detect with 'EyeDropper' in window before wiring handlers so AbortError stays reserved for real dismissals and your fallback path owns the unsupported case.

What exactly comes back in sRGBHex - and what information is deliberately missing?

You get one object with one field: {sRGBHex: '#rrggbb'} - lowercase, six hex digits, always opaque, no alpha (a screen pixel has no transparency to sample; the compositor already flattened it), and no color-space tag: values are sRGB, full stop. What is deliberately absent: the pick coordinates - the API never tells you where on the screen the user clicked, only what color was there, which kills an entire class of screen-scraping uses by design; a region sample - it is one pixel, so a dithered gradient or a JPEG edge gives you a single noisy sample rather than an average; and any wide-gamut value - on a Display-P3 monitor the magnifier shows you the pixel but the answer is quantized down to sRGB, so out-of-gamut neon crushes toward the nearest encodable value. Practical workarounds that respect the design: for stability on noisy edges, have the user pick once, then re-sample near that point in your own canvas if the content is yours; for color management, treat the result as a design-system input (someone picks the color, your toolchain stores it) rather than a real-time measurement device; and if you need P3 math, that is a canvas/display-p3 conversation, not an EyeDropper one.

When should I skip EyeDropper entirely, and what do the alternatives do better?

Four cases, each with a named alternative. Colors inside your own document: getComputedStyle(element).color reads any rendered style with zero prompts, and canvasRenderingContext.getImageData(x, y, 1, 1) samples your own canvas pixels - both work in every browser, no gesture tax, exact values including alpha. Your page displays the image being sampled: draw it to a canvas (same-origin or CORS-clean only - tainted canvases block getImageData) and read pixels directly with hover previews, which is a far better UX than the system magnifier anyway. Cross-browser support is the requirement: <input type=color> is universal, and on Windows, macOS and ChromeOS builds its native dropdown often includes an eyedropper - detect support, and on Firefox or Safari the input is simply the correct answer. High-frequency or automated sampling: EyeDropper is gesture-gated per pick by law-of-the-platform, so any flow needing dozens of samples (batch palette extraction, thumbnail analysis) belongs in canvas or a Worker with ImageBitmap, not in a magnifier loop. The honest sweet spot for the API is precisely the rest of the screen: pick the brand color from a PDF in another window, match a paint chip from a photo in the local viewer, steal a hue from any web page - one click, one pixel, done.

Related tools