Use cases
Building a Color Picker
Create a professional color picker with ColorAssembly
Learn how to build a fully-featured color picker with real-time conversion, accessibility checking, and palette generation.
Overview
We'll build a color picker that:
- Shows color in multiple formats (HEX, RGB, HSL, OKLAB)
- Generates harmonies and variations
- Checks accessibility
- Provides copy-to-clipboard functionality
Basic Implementation
import { useState, useEffect } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
export function ColorPicker() {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
const [color, setColor] = useState('#6366f1');
useEffect(() => {
loadColorAssembly().then(setApi);
}, []);
if (!api) return <div>Loading...</div>;
const rgb = api.transforms.hexToRgb({ hex: color });
const hsl = api.transforms.rgbToHsl(rgb);
const oklch = api.transforms.rgbToOklch(rgb);
return (
<div className="w-full max-w-md space-y-6 p-6 border rounded-lg">
{/* Color Preview */}
<div
className="w-full h-48 rounded-lg shadow-lg"
style={{ backgroundColor: color }}
/>
{/* HTML Color Input */}
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
className="w-full h-12 rounded cursor-pointer"
/>
{/* Color Formats */}
<div className="space-y-3">
<ColorFormat label="HEX" value={color.toUpperCase()} />
<ColorFormat label="RGB" value={`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`} />
<ColorFormat
label="HSL"
value={`hsl(${hsl.h.toFixed(0)}°, ${hsl.s.toFixed(0)}%, ${hsl.l.toFixed(0)}%)`}
/>
<ColorFormat
label="OKLCH"
value={`oklch(${oklch.l.toFixed(2)}, ${oklch.c.toFixed(2)}, ${oklch.h.toFixed(0)})`}
/>
</div>
</div>
);
}
function ColorFormat({ label, value }: { label: string; value: string }) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="flex items-center justify-between p-3 bg-gray-50 rounded">
<div>
<div className="text-xs text-gray-500">{label}</div>
<div className="font-mono text-sm">{value}</div>
</div>
<button
onClick={copy}
className="px-3 py-1 text-xs border rounded hover:bg-gray-100"
>
{copied ? 'Copied!' : 'Copy'}
</button>
</div>
);
}Advanced Features
Color Harmonies
Add harmony generation to your picker:
function ColorHarmonies({ baseColor, api }: { baseColor: string; api: ColorAssemblyAPI }) {
const rgb = api.transforms.hexToRgb({ hex: baseColor });
const harmonies = {
complementary: api.generate.complementary(rgb),
triadic: api.generate.triadic(rgb),
analogous: api.generate.analogous(rgb),
tetradic: api.generate.tetradic(rgb),
};
return (
<div className="space-y-4">
{Object.entries(harmonies).map(([name, colors]) => (
<div key={name}>
<h4 className="text-sm font-semibold capitalize mb-2">{name}</h4>
<div className="flex gap-2">
{colors.map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div
key={i}
className="flex-1 h-16 rounded cursor-pointer hover:scale-105 transition"
style={{ backgroundColor: hex.hex }}
title={hex.hex}
onClick={() => navigator.clipboard.writeText(hex.hex)}
/>
);
})}
</div>
</div>
))}
</div>
);
}Shades and Tints
function ShadesAndTints({ baseColor, api }: { baseColor: string; api: ColorAssemblyAPI }) {
const rgb = api.transforms.hexToRgb({ hex: baseColor });
const shades = api.generate.shades(rgb, 5);
const tints = api.generate.tints(rgb, 5);
return (
<div className="space-y-4">
{/* Shades */}
<div>
<h4 className="text-sm font-semibold mb-2">Shades (Darker)</h4>
<div className="grid grid-cols-5 gap-2">
{shades.map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div key={i} className="space-y-1">
<div
className="w-full h-12 rounded cursor-pointer"
style={{ backgroundColor: hex.hex }}
onClick={() => navigator.clipboard.writeText(hex.hex)}
/>
<div className="text-xs text-center font-mono">{hex.hex}</div>
</div>
);
})}
</div>
</div>
{/* Tints */}
<div>
<h4 className="text-sm font-semibold mb-2">Tints (Lighter)</h4>
<div className="grid grid-cols-5 gap-2">
{tints.map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div key={i} className="space-y-1">
<div
className="w-full h-12 rounded cursor-pointer"
style={{ backgroundColor: hex.hex }}
onClick={() => navigator.clipboard.writeText(hex.hex)}
/>
<div className="text-xs text-center font-mono">{hex.hex}</div>
</div>
);
})}
</div>
</div>
</div>
);
}Accessibility Preview
function AccessibilityPreview({ baseColor, api }: { baseColor: string; api: ColorAssemblyAPI }) {
const rgb = api.transforms.hexToRgb({ hex: baseColor });
const textColor = api.analyze.suggestTextColor(rgb);
const textHex = api.transforms.rgbToHex(textColor);
const contrast = api.analyze.contrastRatio(textColor, rgb);
const wcag = api.analyze.checkContrast(textColor, rgb);
return (
<div className="space-y-4">
{/* Preview */}
<div
className="p-6 rounded-lg"
style={{ backgroundColor: baseColor, color: textHex.hex }}
>
<h3 className="text-2xl font-bold mb-2">Sample Heading</h3>
<p>This is how text will appear on this background color.</p>
</div>
{/* Contrast Info */}
<div className="p-4 bg-gray-50 rounded">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold">Contrast Ratio</span>
<span className="text-2xl font-bold">{contrast.toFixed(2)}:1</span>
</div>
<div className="space-y-1 text-sm">
<div className={wcag.normalTextAA ? 'text-green-600' : 'text-red-600'}>
{wcag.normalTextAA ? '✓' : '✗'} Normal Text AA (4.5:1)
</div>
<div className={wcag.normalTextAAA ? 'text-green-600' : 'text-red-600'}>
{wcag.normalTextAAA ? '✓' : '✗'} Normal Text AAA (7:1)
</div>
<div className={wcag.largeTextAA ? 'text-green-600' : 'text-red-600'}>
{wcag.largeTextAA ? '✓' : '✗'} Large Text AA (3:1)
</div>
</div>
</div>
</div>
);
}Complete Color Picker Component
Putting it all together:
'use client';
import { useState, useEffect } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
export function AdvancedColorPicker() {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
const [color, setColor] = useState('#6366f1');
const [activeTab, setActiveTab] = useState<'formats' | 'harmonies' | 'shades' | 'accessibility'>('formats');
useEffect(() => {
loadColorAssembly().then(setApi);
}, []);
if (!api) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2" />
<p className="text-sm text-gray-600">Loading ColorAssembly...</p>
</div>
</div>
);
}
const tabs = [
{ id: 'formats', label: 'Formats' },
{ id: 'harmonies', label: 'Harmonies' },
{ id: 'shades', label: 'Shades & Tints' },
{ id: 'accessibility', label: 'Accessibility' },
];
return (
<div className="w-full max-w-2xl mx-auto space-y-6 p-6 border rounded-lg shadow-lg">
{/* Header */}
<div>
<h2 className="text-2xl font-bold mb-2">Color Picker</h2>
<p className="text-sm text-gray-600">
Pick a color and explore its properties, harmonies, and accessibility
</p>
</div>
{/* Color Preview */}
<div
className="w-full h-32 rounded-lg shadow-md transition-colors duration-300"
style={{ backgroundColor: color }}
/>
{/* Color Input */}
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
className="w-full h-12 rounded-lg cursor-pointer"
/>
{/* Tabs */}
<div className="flex border-b">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`px-4 py-2 font-medium transition-colors ${
activeTab === tab.id
? 'border-b-2 border-blue-600 text-blue-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Tab Content */}
<div className="py-4">
{activeTab === 'formats' && <ColorFormats color={color} api={api} />}
{activeTab === 'harmonies' && <ColorHarmonies baseColor={color} api={api} />}
{activeTab === 'shades' && <ShadesAndTints baseColor={color} api={api} />}
{activeTab === 'accessibility' && <AccessibilityPreview baseColor={color} api={api} />}
</div>
</div>
);
}Tips
Debounce Color Changes
For better performance with live updates:
import { useState, useEffect } from 'react';
function useDebouncedColor(color: string, delay: number = 300) {
const [debouncedColor, setDebouncedColor] = useState(color);
useEffect(() => {
const timer = setTimeout(() => setDebouncedColor(color), delay);
return () => clearTimeout(timer);
}, [color, delay]);
return debouncedColor;
}Save Recent Colors
function useRecentColors() {
const [recent, setRecent] = useState<string[]>([]);
const addColor = (color: string) => {
setRecent((prev) => {
const filtered = prev.filter((c) => c !== color);
return [color, ...filtered].slice(0, 10);
});
};
return { recent, addColor };
}Next Steps
- Explore Design System generator
- Build an Accessibility Checker
- Learn about Theme Generation