Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Color Picker
Color picker dialog with preset swatches, custom hex/RGB input, opacity slider, recent colors history, and copy-to-clipboard
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Pick the perfect color every time. This React color picker dialog provides a comprehensive color selection interface with preset color swatches, custom hex and RGB input fields, opacity/alpha slider, recent colors history, and one-click copy functionality. Built with shadcn/ui Dialog, Input, Button, Slider, Tabs, and Tooltip components using Tailwind CSS, designers and developers select colors with precision and convenience. Preset swatches, custom input, recent history—perfect for design tools, theming systems, content editors, or any Next.js application where accurate color selection enhances creative workflows and brand consistency.
"use client";import { useState, useEffect } from "react";import { Palette, Copy, Check, Pipette, RotateCcw,} from "lucide-react";import { Button } from "@/components/ui/button";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { Slider } from "@/components/ui/slider";import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/components/ui/tooltip";import { Separator } from "@/components/ui/separator";import { cn } from "@/lib/utils";const presetColors = [ // Reds ["#FEE2E2", "#FECACA", "#FCA5A5", "#F87171", "#EF4444", "#DC2626"], // Oranges ["#FFEDD5", "#FED7AA", "#FDBA74", "#FB923C", "#F97316", "#EA580C"], // Yellows ["#FEF9C3", "#FEF08A", "#FDE047", "#FACC15", "#EAB308", "#CA8A04"], // Greens ["#DCFCE7", "#BBF7D0", "#86EFAC", "#4ADE80", "#22C55E", "#16A34A"], // Blues ["#DBEAFE", "#BFDBFE", "#93C5FD", "#60A5FA", "#3B82F6", "#2563EB"], // Purples ["#F3E8FF", "#E9D5FF", "#D8B4FE", "#C084FC", "#A855F7", "#9333EA"], // Grays ["#F9FAFB", "#F3F4F6", "#E5E7EB", "#D1D5DB", "#9CA3AF", "#6B7280"],];const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), } : null;};const rgbToHex = (r: number, g: number, b: number): string => { return "#" + [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("").toUpperCase();};export const title = "React Dialog Block Color Picker";export default function DialogColorPicker() { const [open, setOpen] = useState(false); const [color, setColor] = useState("#3B82F6"); const [opacity, setOpacity] = useState(100); const [hexInput, setHexInput] = useState("#3B82F6"); const [rgbInput, setRgbInput] = useState({ r: 59, g: 130, b: 246 }); const [recentColors, setRecentColors] = useState<string[]>([ "#3B82F6", "#EF4444", "#22C55E", "#F97316", "#A855F7", ]); const [copied, setCopied] = useState(false); useEffect(() => { const rgb = hexToRgb(color); if (rgb) { setRgbInput(rgb); setHexInput(color.toUpperCase()); } }, [color]); const handleHexChange = (value: string) => { setHexInput(value); if (/^#[0-9A-Fa-f]{6}$/.test(value)) { setColor(value.toUpperCase()); } }; const handleRgbChange = (channel: "r" | "g" | "b", value: string) => { const num = Math.min(255, Math.max(0, parseInt(value) || 0)); const newRgb = { ...rgbInput, [channel]: num }; setRgbInput(newRgb); setColor(rgbToHex(newRgb.r, newRgb.g, newRgb.b)); }; const handleSwatchClick = (swatchColor: string) => { setColor(swatchColor); }; const handleCopy = (format: string) => { let copyValue = ""; if (format === "hex") { copyValue = opacity < 100 ? `${color}${Math.round(opacity * 2.55).toString(16).padStart(2, "0").toUpperCase()}` : color; } else if (format === "rgb") { copyValue = opacity < 100 ? `rgba(${rgbInput.r}, ${rgbInput.g}, ${rgbInput.b}, ${(opacity / 100).toFixed(2)})` : `rgb(${rgbInput.r}, ${rgbInput.g}, ${rgbInput.b})`; } navigator.clipboard.writeText(copyValue); setCopied(true); setTimeout(() => setCopied(false), 1500); }; const handleSelect = () => { if (!recentColors.includes(color)) { setRecentColors([color, ...recentColors.slice(0, 4)]); } console.log("Selected color:", color, "Opacity:", opacity); setOpen(false); }; const handleClose = () => { setOpen(false); }; return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <Palette className="mr-2 h-4 w-4" /> Pick Color </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <Palette className="h-5 w-5" /> Color Picker </DialogTitle> <DialogDescription> Select a color from presets or enter a custom value. </DialogDescription> </DialogHeader> <div className="space-y-4 py-4"> <div className="flex items-center gap-4"> <div className="h-16 w-16 rounded-lg border shadow-sm shrink-0" style={{ backgroundColor: color, opacity: opacity / 100, }} /> <div className="flex-1 space-y-2"> <div className="flex items-center justify-between"> <span className="text-sm font-medium">{color}</span> <TooltipProvider> <Tooltip> <TooltipTrigger asChild> <Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => handleCopy("hex")} > {copied ? ( <Check className="h-4 w-4 text-primary" /> ) : ( <Copy className="h-4 w-4" /> )} </Button> </TooltipTrigger> <TooltipContent>Copy HEX</TooltipContent> </Tooltip> </TooltipProvider> </div> <div className="flex items-center gap-2"> <span className="text-xs text-muted-foreground">Opacity</span> <Slider value={[opacity]} onValueChange={([value]) => setOpacity(value)} max={100} step={1} className="flex-1" /> <span className="text-xs w-8 text-right">{opacity}%</span> </div> </div> </div> <Tabs defaultValue="swatches" className="w-full"> <TabsList className="grid w-full grid-cols-2"> <TabsTrigger value="swatches">Swatches</TabsTrigger> <TabsTrigger value="custom">Custom</TabsTrigger> </TabsList> <TabsContent value="swatches" className="space-y-3 mt-3"> {presetColors.map((row, rowIndex) => ( <div key={rowIndex} className="flex justify-center gap-1.5"> {row.map((swatchColor) => ( <button key={swatchColor} onClick={() => handleSwatchClick(swatchColor)} className={cn( "h-7 w-7 rounded-md border transition-all hover:scale-110", color === swatchColor && "ring-2 ring-primary ring-offset-2" )} style={{ backgroundColor: swatchColor }} /> ))} </div> ))} </TabsContent> <TabsContent value="custom" className="space-y-4 mt-3"> <div className="space-y-2"> <Label htmlFor="hex">HEX</Label> <Input id="hex" value={hexInput} onChange={(e) => handleHexChange(e.target.value)} placeholder="#000000" maxLength={7} /> </div> <div className="grid grid-cols-3 gap-3"> <div className="space-y-2"> <Label htmlFor="r">R</Label> <Input id="r" type="number" min={0} max={255} value={rgbInput.r} onChange={(e) => handleRgbChange("r", e.target.value)} /> </div> <div className="space-y-2"> <Label htmlFor="g">G</Label> <Input id="g" type="number" min={0} max={255} value={rgbInput.g} onChange={(e) => handleRgbChange("g", e.target.value)} /> </div> <div className="space-y-2"> <Label htmlFor="b">B</Label> <Input id="b" type="number" min={0} max={255} value={rgbInput.b} onChange={(e) => handleRgbChange("b", e.target.value)} /> </div> </div> <Button variant="outline" size="sm" className="w-full" onClick={() => handleCopy("rgb")} > <Copy className="mr-2 h-4 w-4" /> Copy as RGB </Button> </TabsContent> </Tabs> {recentColors.length > 0 && ( <> <Separator /> <div className="space-y-2"> <div className="flex items-center justify-between"> <Label className="text-xs text-muted-foreground">Recent Colors</Label> <Button variant="ghost" size="sm" className="h-6 text-xs" onClick={() => setRecentColors([])} > <RotateCcw className="mr-1 h-3 w-3" /> Clear </Button> </div> <div className="flex gap-2"> {recentColors.map((recentColor, index) => ( <button key={index} onClick={() => handleSwatchClick(recentColor)} className={cn( "h-8 w-8 rounded-md border transition-all hover:scale-110", color === recentColor && "ring-2 ring-primary ring-offset-2" )} style={{ backgroundColor: recentColor }} /> ))} </div> </div> </> )} </div> <DialogFooter className="gap-2 sm:gap-0"> <Button variant="outline" onClick={handleClose}> Cancel </Button> <Button onClick={handleSelect}> Select Color </Button> </DialogFooter> </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-color-picker.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-color-picker.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-color-picker.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-color-picker.jsonRelated blocks you will also like
React Dialog Block Edit Profile Form
Profile customization
React Dialog Block Quick Note
Color-coded notes
React Dialog Block Create Workspace
Workspace theming
React Dialog Block Notification Preferences
Preferences interface
React Dialog Block Success Confirmation
Selection confirmation
React Dialog Block Keyboard Shortcuts
Tool shortcuts