Fiber UI LogoFiberUI

useEyeDropper

A specialized utility hook for selecting colors from anywhere on the screen using the modern EyeDropper API, providing hex color codes effortlessly.

Installation

npx shadcn@latest add https://r.fiberui.com/r/hooks/use-eye-dropper.json

A React hook that provides access to the EyeDropper API, allowing users to sample colors from anywhere on their screen—even outside the browser window.

Browser Support

This API is currently supported primarily in Chromium-based browsers (Chrome, Edge, Opera) on desktop. It is not supported on Firefox or Safari yet. Use isSupported to provide a fallback.

Features

  • Pick Anywhere - Select colors from other apps, desktop wallpaper, etc.
  • Native UI - Uses the browser's native magnifying glass UI
  • Support Check - isSupported flag for progressive enhancement
  • Type Safe - Fully typed for TypeScript

Basic Usage

A color picker button that samples any pixel on the screen.

EyeDropper API is not supported in this browser (Chrome/Edge only).
"use client";

import { useEyeDropper } from "@repo/hooks/utility/use-eye-dropper";
import { Button } from "@repo/ui/components/button";
import { Pipette } from "lucide-react";
import { toast } from "sonner";

export function Example1() {
    const { open, isSupported, color, isLoading } = useEyeDropper();

    const handlePickColor = async () => {
        try {
            const result = await open();
            if (result) {
                toast.success(`Color picked: ${result.sRGBHex}`);
            }
        } catch {
            // Error is already handled in hook, but we can access it here too
            toast.error("Failed to pick color");
        }
    };

    if (!isSupported) {
        return (
            <div className="rounded-md bg-yellow-100 p-4 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-200">
                EyeDropper API is not supported in this browser (Chrome/Edge
                only).
            </div>
        );
    }

    return (
        <div className="flex flex-col items-center gap-4">
            <div
                className="h-32 w-32 rounded-xl border-4 border-white shadow-lg transition-colors duration-200 dark:border-zinc-800"
                style={{ backgroundColor: color || "#000000" }}
            />
            <div className="flex items-center gap-2">
                <Button
                    onClick={handlePickColor}
                    isDisabled={isLoading}
                    className="gap-2"
                >
                    <Pipette className="h-4 w-4" />
                    {isLoading ? "Picking..." : "Pick Color"}
                </Button>
                {color && (
                    <div className="rounded bg-zinc-100 px-2 py-1 font-mono text-sm dark:bg-zinc-800">
                        {color}
                    </div>
                )}
            </div>
            <p className="max-w-xs text-center text-sm text-zinc-500">
                Click the button and select any pixel on your screen (even
                outside the browser!)
            </p>
        </div>
    );
}

API Reference

Hook Signature

function useEyeDropper(): UseEyeDropperReturn;

Return Value

PropertyTypeDescription
open(options?) => Promise<EyeDropperResult | null>Opens the eye dropper mode. Returns result object
isSupportedbooleantrue if EyeDropper API is available
isLoadingbooleantrue while selecting a color
colorstring | nullThe hex sRGB color string (e.g. #ff0000)
errorError | nullError object if selection failed or cancelled

EyeDropperResult

interface EyeDropperResult {
    sRGBHex: string; // The selected color in hex format
}

Hook Source Code

import { useState, useCallback } from "react";

/**
 * Options for opening the EyeDropper
 */
export interface UseEyeDropperOptions {
    /** Signal to abort the selection */
    signal?: AbortSignal;
}

/**
 * Result from the EyeDropper API
 */
export interface EyeDropperResult {
    /** The selected color in hex sRGB format (e.g. #ff0000) */
    sRGBHex: string;
}

/**
 * Return type for the useEyeDropper hook
 */
export interface UseEyeDropperReturn {
    /** Open the eye dropper to select a color */
    open: (options?: UseEyeDropperOptions) => Promise<EyeDropperResult | null>;
    /** Whether the EyeDropper API is supported */
    isSupported: boolean;
    /** Whether the eye dropper is currently open */
    isLoading: boolean;
    /** The last selected color */
    color: string | null;
    /** Error from the last operation */
    error: Error | null;
}

// Add type definition for EyeDropper if not present in environment
interface EyeDropper {
    open(options?: { signal?: AbortSignal }): Promise<{ sRGBHex: string }>;
}

interface EyeDropperConstructor {
    new (): EyeDropper;
    prototype: EyeDropper;
}

declare global {
    interface Window {
        EyeDropper?: EyeDropperConstructor;
    }
}

/**
 * A React hook for using the EyeDropper API to select colors from the screen.
 *
 * @returns UseEyeDropperReturn object with open function and state
 *
 * @example
 * ```tsx
 * const { open, color, isSupported } = useEyeDropper();
 *
 * return (
 *     <div>
 *         <button onClick={open} disabled={!isSupported}>Pick Color</button>
 *         {color && <div style={{ backgroundColor: color }}>Selected: {color}</div>}
 *     </div>
 * );
 * ```
 */
export function useEyeDropper(): UseEyeDropperReturn {
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState<Error | null>(null);
    const [color, setColor] = useState<string | null>(null);

    // Check support
    const isSupported = typeof window !== "undefined" && "EyeDropper" in window;

    // Open eye dropper
    const open = useCallback(
        async (
            options?: UseEyeDropperOptions,
        ): Promise<EyeDropperResult | null> => {
            if (!isSupported) {
                setError(new Error("EyeDropper API is not supported"));
                return null;
            }

            setIsLoading(true);
            setError(null);

            try {
                // @ts-ignore - EyeDropper is experimental
                const eyeDropper = new window.EyeDropper();
                const result = await eyeDropper.open(options);
                setColor(result.sRGBHex);
                return result;
            } catch (err) {
                // User cancelation is common error
                const error =
                    err instanceof Error
                        ? err
                        : new Error("Failed to pick color");
                setError(error);
                return null;
            } finally {
                setIsLoading(false);
            }
        },
        [isSupported],
    );

    return {
        open,
        isSupported,
        isLoading,
        color,
        error,
    };
}

export default useEyeDropper;