Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/color-picker.jsonInstalls to @/components/ui/color-picker.
Manual installation
Install the dependencies:
pnpm add react-colorful@^5.6.1Add the components it builds on: input, popover.
Copy the source into your project:
'use client'
import * as React from 'react'
import { HexColorPicker } from 'react-colorful'
import { Input } from '@/components/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { cn } from '@/lib/utils'
type ColorPickerContextValue = {
value: string
setValue: (value: string) => void
onBlur?: () => void
disabled?: boolean
}
const ColorPickerContext = React.createContext<ColorPickerContextValue>({
value: '#000000',
setValue: () => {},
disabled: false,
})
type ColorPickerProps = React.ComponentProps<typeof Popover> & {
value?: string
onValueChange?: (value: string) => void
defaultValue?: string
onBlur?: () => void
disabled?: boolean
}
function ColorPicker({
open,
value: valueProp,
onValueChange,
defaultValue,
onBlur,
disabled,
...props
}: ColorPickerProps) {
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? '#000000')
const value = valueProp ?? uncontrolledValue
const setValue = React.useCallback(
(next: string) => {
if (valueProp === undefined) setUncontrolledValue(next)
onValueChange?.(next)
},
[valueProp, onValueChange],
)
const context = React.useMemo(() => ({ value, setValue, disabled, onBlur }), [value, setValue, disabled, onBlur])
return (
<ColorPickerContext.Provider value={context}>
<Popover open={disabled ? false : open} {...props} />
</ColorPickerContext.Provider>
)
}
function ColorPickerInput({
disabled: disabledProp,
onChange,
onBlur: onBlurProp,
...props
}: React.ComponentProps<typeof Input>) {
const { value, setValue, disabled, onBlur } = React.useContext(ColorPickerContext)
return (
<Input
data-slot="color-picker-input"
value={value}
disabled={disabledProp ?? disabled}
onChange={(event) => {
onChange?.(event)
if (!event.defaultPrevented) setValue(event.target.value)
}}
onBlur={(event) => {
onBlurProp?.(event)
if (!event.defaultPrevented) onBlur?.()
}}
{...props}
/>
)
}
function ColorPickerTrigger({
className,
disabled: disabledProp,
children,
style,
...props
}: React.ComponentProps<typeof PopoverTrigger>) {
const { value, disabled } = React.useContext(ColorPickerContext)
return (
<PopoverTrigger
data-slot="color-picker-trigger"
className={cn(
'relative aspect-square size-10 shrink-0 rounded-lg border border-input bg-background bg-clip-padding transition-colors outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
className,
)}
style={{ backgroundColor: value, ...style }}
disabled={disabledProp ?? disabled}
aria-disabled={disabledProp ?? disabled}
{...props}
>
<span className="sr-only">Pick a color</span>
{children}
</PopoverTrigger>
)
}
function ColorPickerContent({ className, children, ...props }: React.ComponentProps<typeof PopoverContent>) {
const { value, setValue } = React.useContext(ColorPickerContext)
return (
<PopoverContent data-slot="color-picker-content" className={cn('w-auto p-2', className)} {...props}>
<HexColorPicker color={value} onChange={setValue} />
{children}
</PopoverContent>
)
}
export { ColorPicker, ColorPickerInput, ColorPickerTrigger, ColorPickerContent }
export type { ColorPickerProps }A color picker component that allows users to select colors using a color input and a color picker popover.
Features
- Color input field for direct hex color input
- Color picker popover with a visual color picker
- Color preview trigger button
- Fully accessible
- Keyboard navigation support
- Customizable styling
Usage
import { ColorPicker, ColorPickerInput, ColorPickerTrigger, ColorPickerContent } from '@/components/ui/color-picker'
export default function MyComponent() {
return (
<ColorPicker defaultValue="#1f6f8b">
<div className="flex w-full items-center gap-2">
<ColorPickerInput aria-label="Accent colour" />
<ColorPickerTrigger />
</div>
<ColorPickerContent />
</ColorPicker>
)
}Examples
Default
A labelled colour field: type a hex code or pick a colour from the popover.
With Presets
Extra children of ColorPickerContent render below the picker. Here they offer a palette of preset colours, controlled with value and onValueChange.
Controlled
Several controlled pickers in a list, each with only a trigger. Give each trigger an aria-label that names what it colours.
Disabled
Invalid
Pass aria-invalid to the input and trigger to show the error state, and explain the error in a FieldError.
Props
ColorPicker
-
value:
string
The controlled value of the color picker. -
defaultValue:
string
The default value of the color picker. -
onValueChange:
(value: string) => void
Callback fired when the value changes. -
open:
boolean
The controlled open state of the popover. -
defaultOpen:
boolean
The default open state of the popover. -
onOpenChange:
(open: boolean, eventDetails) => void
Callback fired when the open state changes (Base UI Popover). -
disabled:
boolean
Disables the input and the trigger, and keeps the popover closed. -
onBlur:
() => void
Called when the input loses focus (handy for form libraries).
ColorPickerInput
Extends the Input component props. Your own onChange/onBlur run first; call event.preventDefault() to stop the picker from updating its value (or calling the root onBlur).
ColorPickerTrigger
Extends the PopoverTrigger component props. A size-8 (h-8, rounded-lg) swatch showing the current colour, with the same focus ring and aria-invalid styles as Input.
ColorPickerContent
Extends the PopoverContent component props. Extra children render below the picker.