ColorPicker
Select colors using an intuitive color picker interface with hex value display. Perfect for theme customization, design tools, and any application requiring color input. Built for Pulse Framework with full reactivity support.
Import
ts
import { ColorPicker } from '@odyssee/components';Basic Usage
Code Éditable
Résultat
With Value Display
Show the hex color value alongside the picker.
Code Éditable
Résultat
Sizes
Available sizes: xs, sm, md, lg, xl.
Code Éditable
Résultat
With Hints
Code Éditable
Résultat
Validation State
Code Éditable
Résultat
Disabled State
Code Éditable
Résultat
Reactive Color Picker
Control color with Pulse signals.
tsx
import { ColorPicker, Card, Button, Pulse } from '@odyssee/components';
const ThemeCustomizer = () => {
const primaryColor = Pulse.signal('#3b82f6');
const secondaryColor = Pulse.signal('#10b981');
const accentColor = Pulse.signal('#f59e0b');
const previewStyle = Pulse.computed(() => ({
background: `linear-gradient(135deg, ${primaryColor()} 0%, ${secondaryColor()} 50%, ${accentColor()} 100%)`
}));
const resetColors = () => {
primaryColor('#3b82f6');
secondaryColor('#10b981');
accentColor('#f59e0b');
};
const randomizeColors = () => {
const randomColor = () => {
return '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
};
primaryColor(randomColor());
secondaryColor(randomColor());
accentColor(randomColor());
};
return (
<div class='space-y-6'>
{/* Preview */}
<div
class='h-48 rounded-lg shadow-lg'
style={previewStyle()}
/>
{/* Color Pickers */}
<div class='space-y-4'>
<ColorPicker
label='Primary Color'
value={primaryColor}
showValue={true}
onChange={(val) => primaryColor(val)}
/>
<ColorPicker
label='Secondary Color'
value={secondaryColor}
showValue={true}
onChange={(val) => secondaryColor(val)}
/>
<ColorPicker
label='Accent Color'
value={accentColor}
showValue={true}
onChange={(val) => accentColor(val)}
/>
</div>
{/* Actions */}
<div class='flex gap-3'>
<Button onClick={resetColors} variant='outline'>
Reset Colors
</Button>
<Button onClick={randomizeColors} variant='outline'>
Randomize
</Button>
</div>
{/* Color Info */}
<div class='p-4 bg-gray-50 rounded-lg dark:bg-neutral-800'>
<h4 class='font-semibold mb-2'>Selected Colors</h4>
<div class='space-y-1 text-sm font-mono'>
<p>Primary: {primaryColor()}</p>
<p>Secondary: {secondaryColor()}</p>
<p>Accent: {accentColor()}</p>
</div>
</div>
</div>
);
};Complete Example: Brand Color Manager
tsx
import { ColorPicker, Card, Button, Badge, Pulse } from '@odyssee/components';
const BrandColorManager = () => {
const brandColors = Pulse.signal([
{ id: 1, name: 'Primary', color: '#3b82f6', usage: 'Buttons, Links' },
{ id: 2, name: 'Secondary', color: '#10b981', usage: 'Success states' },
{ id: 3, name: 'Accent', color: '#f59e0b', usage: 'Highlights' },
{ id: 4, name: 'Danger', color: '#ef4444', usage: 'Errors, Warnings' }
]);
const updateColor = (id, newColor) => {
brandColors(
brandColors().map(item =>
item.id === id ? { ...item, color: newColor } : item
)
);
};
const exportColors = () => {
const css = brandColors().map(item =>
`--color-${item.name.toLowerCase()}: ${item.color};`
).join('\n');
alert('CSS Variables:\n' + css);
};
const contrastRatio = (color) => {
// Simple contrast calculation
const hex = color.replace('#', '');
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? 'dark' : 'light';
};
return (
<Card title='Brand Color System' size='lg'>
<div class='space-y-6'>
{/* Color Grid */}
<div class='grid grid-cols-1 md:grid-cols-2 gap-4'>
{brandColors().map(item => (
<div
key={item.id}
class='p-4 border rounded-lg dark:border-neutral-700'
>
<div class='flex items-start justify-between mb-3'>
<div>
<h4 class='font-semibold text-gray-900 dark:text-white'>
{item.name}
</h4>
<p class='text-xs text-gray-600 dark:text-gray-400'>
{item.usage}
</p>
</div>
<Badge
variant='soft'
color={contrastRatio(item.color) === 'light' ? 'primary' : 'secondary'}
>
{contrastRatio(item.color)}
</Badge>
</div>
{/* Color Preview */}
<div
class='h-20 rounded-lg mb-3 border-2 border-gray-200 dark:border-neutral-600'
style={{ backgroundColor: item.color }}
/>
{/* Color Picker */}
<ColorPicker
value={item.color}
showValue={true}
size='sm'
onChange={(color) => updateColor(item.id, color)}
/>
</div>
))}
</div>
{/* Actions */}
<div class='flex gap-3 pt-4 border-t dark:border-neutral-700'>
<Button onClick={exportColors} color='primary'>
Export as CSS
</Button>
<Button variant='outline'>
Save Color Palette
</Button>
</div>
{/* Accessibility Info */}
<div class='p-4 bg-blue-50 rounded-lg dark:bg-blue-900/20'>
<p class='text-sm text-blue-900 dark:text-blue-200'>
💡 Tip: Ensure sufficient contrast ratio (4.5:1) for text on colored backgrounds
</p>
</div>
</div>
</Card>
);
};Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Signal<string> | "#000000" | Color value (hex format) |
label | string | - | Picker label text |
hint | string | - | Helper text below picker |
error | string | - | Error message |
disabled | boolean | false | Disable picker |
required | boolean | false | Mark as required field |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Picker size |
showValue | boolean | true | Display hex value |
onChange | (color: string) => void | - | Color change callback |
name | string | - | Input name attribute |
className | string | - | Additional CSS classes |
id | string | Auto-generated | HTML id attribute |
Accessibility
The ColorPicker component follows accessibility best practices:
- ✅ Semantic HTML with native color input
- ✅ Proper label associations
- ✅ Keyboard accessible (Enter/Space to open picker)
- ✅ Focus indicators
- ✅ Screen reader friendly
- ✅ Disabled state properly communicated
tsx
// Accessibility features are built-in
const accessiblePicker = (
<ColorPicker
label='Choose theme color'
value='#3b82f6'
required={true}
// All ARIA and keyboard navigation handled automatically
/>
);Best Practices
✅ Do
- Provide clear labels describing the color's purpose
- Show the hex value for reference
- Consider color accessibility and contrast
- Validate color values if needed
- Provide hints about color usage
tsx
// Good: Clear purpose and context
const goodColorPicker = (
<ColorPicker
label='Primary Button Color'
value='#3b82f6'
showValue={true}
hint='Used for primary actions throughout the app'
/>
);❌ Don't
- Don't omit labels (unclear purpose)
- Don't hide hex values for technical users
- Don't forget accessibility considerations
- Don't use colors that fail contrast requirements
- Don't allow invalid color formats
tsx
// Bad: No context or validation
const badColorPicker = (
<ColorPicker
value='#ff0000'
// No label
// No validation
// No accessibility check
/>
);
// Better: Clear and validated
const betterColorPicker = (
<ColorPicker
label='Alert Color'
value='#ef4444'
showValue={true}
hint='Ensure sufficient contrast with white text'
/>
);Use Cases
Theme Customizer
tsx
const ThemeEditor = () => {
const theme = Pulse.signal({
primary: '#3b82f6',
secondary: '#10b981',
background: '#ffffff',
text: '#1f2937'
});
const updateThemeColor = (key, color) => {
theme({ ...theme(), [key]: color });
};
return (
<div class='space-y-4'>
<ColorPicker
label='Primary Color'
value={theme().primary}
showValue={true}
onChange={(color) => updateThemeColor('primary', color)}
/>
<ColorPicker
label='Background Color'
value={theme().background}
showValue={true}
onChange={(color) => updateThemeColor('background', color)}
/>
</div>
);
};Color Palette Generator
tsx
const PaletteGenerator = () => {
const baseColor = Pulse.signal('#3b82f6');
const palette = Pulse.computed(() => {
const base = baseColor();
// Generate shades (simplified)
return {
lightest: base + '20',
light: base + '60',
base: base,
dark: base + 'cc',
darkest: base + 'ff'
};
});
return (
<div class='space-y-4'>
<ColorPicker
label='Base Color'
value={baseColor}
showValue={true}
onChange={(color) => baseColor(color)}
/>
<div class='grid grid-cols-5 gap-2'>
{Object.entries(palette()).map(([name, color]) => (
<div key={name} class='text-center'>
<div
class='h-16 rounded-lg border-2'
style={{ backgroundColor: color }}
/>
<p class='text-xs mt-1'>{name}</p>
</div>
))}
</div>
</div>
);
};Design System Builder
tsx
const DesignSystemColors = () => {
const systemColors = Pulse.signal({
brand: '#3b82f6',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
info: '#06b6d4',
neutral: '#6b7280'
});
const updateSystemColor = (key, value) => {
systemColors({ ...systemColors(), [key]: value });
};
return (
<Card title='Design System Colors'>
<div class='grid grid-cols-2 gap-4'>
{Object.entries(systemColors()).map(([key, value]) => (
<ColorPicker
key={key}
label={key.charAt(0).toUpperCase() + key.slice(1)}
value={value}
showValue={true}
onChange={(color) => updateSystemColor(key, color)}
/>
))}
</div>
</Card>
);
};Styling & Theming
All color picker styles use Tailwind CSS and support dark mode automatically.
Custom Styling
tsx
const customPicker = (
<ColorPicker
label='Custom styled'
value='#3b82f6'
className='border-4 rounded-xl'
/>
);Dark Mode
tsx
// Dark mode support is automatic
const darkModePicker = (
<ColorPicker
label='Theme Color'
value='#3b82f6'
showValue={true}
// Automatically uses dark:border-gray-600, dark:text-gray-300, etc.
/>
);TypeScript
Full TypeScript support with complete type definitions.
tsx
import type { ColorPickerProps } from '@odyssee/components';
import { Pulse } from '@odyssee/components';
// Type-safe props
const props: ColorPickerProps = {
label: 'Brand Color',
value: '#3b82f6',
size: 'md',
showValue: true,
required: false,
onChange: (color: string): void => {
console.log(color);
}
};
const picker = <ColorPicker {...props} />;
// Type-safe with signals
const color = Pulse.signal<string>('#10b981');
const typedPicker = (
<ColorPicker
label='Color'
value={color}
onChange={(val: string) => color(val)}
/>
);
// Type-safe color validation
const isValidHex = (color: string): boolean => {
return /^#[0-9A-F]{6}$/i.test(color);
};
const validateColor = (color: string) => {
if (!isValidHex(color)) {
throw new Error('Invalid hex color format');
}
};Related Components
- Input - Text input for hex values
- RangeSlider - RGB value sliders
- Select - Predefined color selection
- Button - Color-themed buttons
Version: 1.0.0
Last Updated: January 2025