ColorAssembly
Frameworks

React Integration

Using ColorAssembly with React and React hooks

Complete guide for integrating ColorAssembly in your React applications.

Installation

npm i@CymbolTech/colorassembly

Basic 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

On this page