Frameworks
React Integration
Using ColorAssembly with React and React hooks
Complete guide for integrating ColorAssembly in your React applications.
Installation
npm i@CymbolTech/colorassemblyBasic Setup
Create a Custom Hook
Create a reusable hook to manage ColorAssembly instance:
// hooks/useColorAssembly.ts
import { useState, useEffect } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
export function useColorAssembly() {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
loadColorAssembly()
.then(setApi)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { api, loading, error };
}Use in Components
// components/ColorConverter.tsx
import { useState } from 'react';
import { useColorAssembly } from '@/hooks/useColorAssembly';
export function ColorConverter() {
const { api, loading } = useColorAssembly();
const [hex, setHex] = useState('#6366f1');
if (loading) return <div>Loading ColorAssembly...</div>;
if (!api) return <div>Failed to load</div>;
const rgb = api.transforms.hexToRgb({ hex });
const hsl = api.transforms.rgbToHsl(rgb);
return (
<div className="space-y-4">
<input
type="color"
value={hex}
onChange={(e) => setHex(e.target.value)}
className="w-full h-12 rounded cursor-pointer"
/>
<div className="space-y-2 font-mono text-sm">
<div>HEX: {hex}</div>
<div>RGB: rgb({rgb.r}, {rgb.g}, {rgb.b})</div>
<div>HSL: hsl({hsl.h.toFixed(0)}°, {hsl.s.toFixed(0)}%, {hsl.l.toFixed(0)}%)</div>
</div>
</div>
);
}Advanced Patterns
Context Provider
For app-wide access, create a context:
// context/ColorAssemblyContext.tsx
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
const ColorAssemblyContext = createContext<ColorAssemblyAPI | null>(null);
export function ColorAssemblyProvider({ children }: { children: ReactNode }) {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
useEffect(() => {
loadColorAssembly().then(setApi);
}, []);
if (!api) {
return <div>Loading...</div>;
}
return (
<ColorAssemblyContext.Provider value={api}>
{children}
</ColorAssemblyContext.Provider>
);
}
export function useColorAssemblyContext() {
const context = useContext(ColorAssemblyContext);
if (!context) {
throw new Error('useColorAssemblyContext must be used within ColorAssemblyProvider');
}
return context;
}Use in your app:
// App.tsx
import { ColorAssemblyProvider } from './context/ColorAssemblyContext';
function App() {
return (
<ColorAssemblyProvider>
<YourComponents />
</ColorAssemblyProvider>
);
}Accessibility Checker Component
// components/AccessibilityChecker.tsx
import { useState } from 'react';
import { useColorAssemblyContext } from '@/context/ColorAssemblyContext';
export function AccessibilityChecker() {
const api = useColorAssemblyContext();
const [fgColor, setFgColor] = useState('#000000');
const [bgColor, setBgColor] = useState('#ffffff');
const fgRgb = api.transforms.hexToRgb({ hex: fgColor });
const bgRgb = api.transforms.hexToRgb({ hex: bgColor });
const result = api.analyze.checkContrast(fgRgb, bgRgb);
return (
<div className="p-6 rounded-lg border space-y-4">
<h3 className="text-lg font-semibold">Contrast Checker</h3>
<div className="flex gap-4">
<div>
<label className="block text-sm mb-2">Foreground</label>
<input
type="color"
value={fgColor}
onChange={(e) => setFgColor(e.target.value)}
/>
</div>
<div>
<label className="block text-sm mb-2">Background</label>
<input
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
/>
</div>
</div>
{/* Preview */}
<div
className="p-4 rounded"
style={{ backgroundColor: bgColor, color: fgColor }}
>
Sample text preview
</div>
{/* Results */}
<div className="space-y-2">
<div className="text-2xl font-bold">
Contrast Ratio: {result.ratio.toFixed(2)}:1
</div>
<div className="space-y-1 text-sm">
<div className={result.normalTextAA ? 'text-green-600' : 'text-red-600'}>
{result.normalTextAA ? '✓' : '✗'} Normal Text AA (4.5:1)
</div>
<div className={result.normalTextAAA ? 'text-green-600' : 'text-red-600'}>
{result.normalTextAAA ? '✓' : '✗'} Normal Text AAA (7:1)
</div>
<div className={result.largeTextAA ? 'text-green-600' : 'text-red-600'}>
{result.largeTextAA ? '✓' : '✗'} Large Text AA (3:1)
</div>
</div>
</div>
</div>
);
}Color Palette Generator
// components/PaletteGenerator.tsx
import { useState } from 'react';
import { useColorAssemblyContext } from '@/context/ColorAssemblyContext';
export function PaletteGenerator() {
const api = useColorAssemblyContext();
const [baseColor, setBaseColor] = useState('#6366f1');
const rgb = api.transforms.hexToRgb({ hex: baseColor });
const harmonies = api.generate.triadic(rgb);
return (
<div className="space-y-4">
<div>
<label className="block text-sm mb-2">Base Color</label>
<input
type="color"
value={baseColor}
onChange={(e) => setBaseColor(e.target.value)}
className="w-full h-12"
/>
</div>
<div>
<h4 className="font-semibold mb-2">Triadic Harmony</h4>
<div className="grid grid-cols-3 gap-2">
{harmonies.map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div
key={i}
className="h-20 rounded-lg"
style={{ backgroundColor: hex.hex }}
/>
);
})}
</div>
</div>
<div>
<h4 className="font-semibold mb-2">Shades</h4>
<div className="grid grid-cols-5 gap-2">
{api.generate.shades(rgb, 5).map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div
key={i}
className="h-12 rounded"
style={{ backgroundColor: hex.hex }}
/>
);
})}
</div>
</div>
<div>
<h4 className="font-semibold mb-2">Tints</h4>
<div className="grid grid-cols-5 gap-2">
{api.generate.tints(rgb, 5).map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div
key={i}
className="h-12 rounded"
style={{ backgroundColor: hex.hex }}
/>
);
})}
</div>
</div>
</div>
);
}Color Blindness Simulator
// components/ColorBlindnessSimulator.tsx
import { useState } from 'react';
import { useColorAssemblyContext } from '@/context/ColorAssemblyContext';
export function ColorBlindnessSimulator() {
const api = useColorAssemblyContext();
const [color, setColor] = useState('#ff0000');
const rgb = api.transforms.hexToRgb({ hex: color });
const simulations = api.generate.simulateAllColorBlindness(rgb);
const types = [
{ key: 'protanopia', label: 'Protanopia (Red-blind)' },
{ key: 'deuteranopia', label: 'Deuteranopia (Green-blind)' },
{ key: 'tritanopia', label: 'Tritanopia (Blue-blind)' },
{ key: 'achromatopsia', label: 'Achromatopsia (Complete)' },
];
return (
<div className="space-y-4">
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
className="w-full h-12"
/>
<div className="grid grid-cols-2 gap-4">
<div className="text-center">
<div className="font-semibold mb-2">Original</div>
<div
className="h-24 rounded-lg"
style={{ backgroundColor: color }}
/>
</div>
{types.map(({ key, label }) => {
const simColor = simulations[key as keyof typeof simulations];
const hex = api.transforms.rgbToHex(simColor);
return (
<div key={key} className="text-center">
<div className="font-semibold mb-2 text-sm">{label}</div>
<div
className="h-24 rounded-lg"
style={{ backgroundColor: hex.hex }}
/>
</div>
);
})}
</div>
</div>
);
}Performance Tips
Memoization
Use useMemo for expensive calculations:
import { useMemo } from 'react';
function ColorDisplay({ hex }: { hex: string }) {
const { api } = useColorAssembly();
const colors = useMemo(() => {
if (!api) return null;
const rgb = api.transforms.hexToRgb({ hex });
const hsl = api.transforms.rgbToHsl(rgb);
const lab = api.transforms.rgbToLab(rgb);
return { rgb, hsl, lab };
}, [api, hex]);
if (!colors) return null;
return <div>{/* render colors */}</div>;
}Lazy Loading
Load ColorAssembly only when needed:
import { lazy, Suspense } from 'react';
const ColorPicker = lazy(() => import('./components/ColorPicker'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ColorPicker />
</Suspense>
);
}TypeScript Support
Full type safety with ColorAssembly:
import type { RGB, HSL, WCAGContrastResult } from '@cymbol/colorassembly';
interface ColorCardProps {
rgb: RGB;
onSelect: (color: RGB) => void;
}
function ColorCard({ rgb, onSelect }: ColorCardProps) {
const { api } = useColorAssembly();
if (!api) return null;
const hex = api.transforms.rgbToHex(rgb);
const hsl = api.transforms.rgbToHsl(rgb);
return (
<button
onClick={() => onSelect(rgb)}
style={{ backgroundColor: hex.hex }}
className="w-full h-20 rounded-lg"
>
<span className="text-xs">
{hsl.h.toFixed(0)}°, {hsl.s.toFixed(0)}%, {hsl.l.toFixed(0)}%
</span>
</button>
);
}Common Patterns
Debounced Color Input
import { useState, useEffect } from 'react';
import { useColorAssemblyContext } from '@/context/ColorAssemblyContext';
export function DebouncedColorInput() {
const api = useColorAssemblyContext();
const [input, setInput] = useState('#6366f1');
const [debouncedColor, setDebouncedColor] = useState(input);
useEffect(() => {
const timer = setTimeout(() => setDebouncedColor(input), 300);
return () => clearTimeout(timer);
}, [input]);
const rgb = api.transforms.hexToRgb({ hex: debouncedColor });
const hsl = api.transforms.rgbToHsl(rgb);
return (
<div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="#6366f1"
/>
<div>HSL: {hsl.h.toFixed(0)}°, {hsl.s.toFixed(0)}%, {hsl.l.toFixed(0)}%</div>
</div>
);
}Next Steps
- Learn about Next.js integration
- Explore use cases
- Check API reference