Frameworks
Next.js Integration
Using ColorAssembly with Next.js App Router and Pages Router
Complete guide for integrating ColorAssembly in Next.js applications (App Router & Pages Router).
Installation
npm i@CymbolTech/colorassemblyConfiguration
Add WASM support to next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
};
return config;
},
};
module.exports = nextConfig;App Router (Next.js 13+)
Client Component Pattern
// components/ColorConverter.tsx
'use client';
import { useState, useEffect } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
export function ColorConverter() {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
const [hex, setHex] = useState('#6366f1');
useEffect(() => {
loadColorAssembly().then(setApi);
}, []);
if (!api) return <div>Loading...</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"
/>
<div>
<div>RGB: {rgb.r}, {rgb.g}, {rgb.b}</div>
<div>HSL: {hsl.h.toFixed(0)}°, {hsl.s.toFixed(0)}%, {hsl.l.toFixed(0)}%</div>
</div>
</div>
);
}Context Provider Pattern
Create a provider for app-wide access:
// providers/ColorAssemblyProvider.tsx
'use client';
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 className="flex items-center justify-center h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p>Loading ColorAssembly...</p>
</div>
</div>
);
}
return (
<ColorAssemblyContext.Provider value={api}>
{children}
</ColorAssemblyContext.Provider>
);
}
export function useColorAssembly() {
const context = useContext(ColorAssemblyContext);
if (!context) {
throw new Error('useColorAssembly must be used within ColorAssemblyProvider');
}
return context;
}Add to root layout:
// app/layout.tsx
import { ColorAssemblyProvider } from '@/providers/ColorAssemblyProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<ColorAssemblyProvider>
{children}
</ColorAssemblyProvider>
</body>
</html>
);
}Server Actions with ColorAssembly
While ColorAssembly is client-side, you can use it with server actions:
// app/colors/page.tsx
'use client';
import { useState } from 'react';
import { useColorAssembly } from '@/providers/ColorAssemblyProvider';
export default function ColorsPage() {
const api = useColorAssembly();
const [result, setResult] = useState<string>('');
async function generatePalette(formData: FormData) {
const hex = formData.get('color') as string;
const rgb = api.transforms.hexToRgb({ hex });
const palette = api.generate.semanticPalette(rgb, 'light');
// Could send to server action for saving
setResult(JSON.stringify(palette, null, 2));
}
return (
<form action={generatePalette} className="space-y-4">
<input type="color" name="color" defaultValue="#6366f1" />
<button type="submit" className="px-4 py-2 bg-primary text-white rounded">
Generate Palette
</button>
{result && <pre className="bg-gray-100 p-4 rounded">{result}</pre>}
</form>
);
}Pages Router (Next.js 12)
Pages with getStaticProps
// pages/colors/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import { useEffect, useState } from 'react';
import { loadColorAssembly, type ColorAssemblyAPI } from '@cymbol/colorassembly';
interface Props {
colorHex: string;
}
export default function ColorPage({ colorHex }: Props) {
const [api, setApi] = useState<ColorAssemblyAPI | null>(null);
useEffect(() => {
loadColorAssembly().then(setApi);
}, []);
if (!api) return <div>Loading...</div>;
const rgb = api.transforms.hexToRgb({ hex: colorHex });
const hsl = api.transforms.rgbToHsl(rgb);
return (
<div>
<div
className="w-full h-48 rounded-lg"
style={{ backgroundColor: colorHex }}
/>
<div className="mt-4">
<p>RGB: {rgb.r}, {rgb.g}, {rgb.b}</p>
<p>HSL: {hsl.h.toFixed(0)}°, {hsl.s.toFixed(0)}%, {hsl.l.toFixed(0)}%</p>
</div>
</div>
);
}
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [
{ params: { id: 'red' } },
{ params: { id: 'blue' } },
{ params: { id: 'green' } },
],
fallback: true,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const colorMap: Record<string, string> = {
red: '#ff0000',
blue: '#0000ff',
green: '#00ff00',
};
return {
props: {
colorHex: colorMap[params?.id as string] || '#000000',
},
};
};Custom _app.tsx
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { ColorAssemblyProvider } from '@/providers/ColorAssemblyProvider';
export default function App({ Component, pageProps }: AppProps) {
return (
<ColorAssemblyProvider>
<Component {...pageProps} />
</ColorAssemblyProvider>
);
}API Routes
While ColorAssembly is WASM-based (browser-only), you can create API routes for color-related operations:
// app/api/colors/route.ts
import { NextRequest, NextResponse } from 'next/server';
// This would need a Node.js compatible color library
// or return data for client-side ColorAssembly processing
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const hex = searchParams.get('hex');
// Return color data for client to process
return NextResponse.json({
hex,
// Client will use ColorAssembly to transform
});
}Dynamic Theme Generator
// app/theme/page.tsx
'use client';
import { useState } from 'react';
import { useColorAssembly } from '@/providers/ColorAssemblyProvider';
export default function ThemeGeneratorPage() {
const api = useColorAssembly();
const [baseColor, setBaseColor] = useState('#6366f1');
const rgb = api.transforms.hexToRgb({ hex: baseColor });
const palette = api.generate.semanticPalette(rgb, 'light');
const scale = api.generate.primaryScale(rgb);
return (
<div className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">Theme Generator</h1>
<div className="mb-8">
<label className="block mb-2 font-semibold">Base Color</label>
<input
type="color"
value={baseColor}
onChange={(e) => setBaseColor(e.target.value)}
className="w-full h-16 rounded-lg cursor-pointer"
/>
</div>
<div className="grid md:grid-cols-2 gap-8">
{/* Semantic Palette */}
<div>
<h2 className="text-xl font-semibold mb-4">Semantic Colors</h2>
<div className="space-y-2">
{Object.entries(palette).map(([name, rgb]) => {
const hex = api.transforms.rgbToHex(rgb);
return (
<div key={name} className="flex items-center gap-4">
<div
className="w-16 h-16 rounded-lg border"
style={{ backgroundColor: hex.hex }}
/>
<div>
<div className="font-semibold capitalize">{name}</div>
<div className="text-sm text-gray-600">{hex.hex}</div>
</div>
</div>
);
})}
</div>
</div>
{/* Color Scale */}
<div>
<h2 className="text-xl font-semibold mb-4">Primary Scale</h2>
<div className="grid grid-cols-5 gap-2">
{scale.map((color, i) => {
const hex = api.transforms.rgbToHex(color);
return (
<div key={i} className="text-center">
<div
className="w-full h-16 rounded-lg mb-2"
style={{ backgroundColor: hex.hex }}
/>
<div className="text-xs text-gray-600">{(i + 1) * 100}</div>
</div>
);
})}
</div>
</div>
</div>
{/* Export Button */}
<button
onClick={() => {
const css = Object.entries(palette).map(([name, rgb]) => {
const hex = api.transforms.rgbToHex(rgb);
return ` --color-${name}: ${hex.hex};`;
}).join('\n');
const fullCss = `:root {\n${css}\n}`;
navigator.clipboard.writeText(fullCss);
alert('CSS variables copied to clipboard!');
}}
className="mt-8 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Export as CSS Variables
</button>
</div>
);
}Real-time Accessibility Dashboard
// app/accessibility/page.tsx
'use client';
import { useState } from 'react';
import { useColorAssembly } from '@/providers/ColorAssemblyProvider';
export default function AccessibilityDashboard() {
const api = useColorAssembly();
const [fgColor, setFgColor] = useState('#000000');
const [bgColor, setBgColor] = useState('#ffffff');
const fg = api.transforms.hexToRgb({ hex: fgColor });
const bg = api.transforms.hexToRgb({ hex: bgColor });
const wcag = api.analyze.checkContrast(fg, bg);
const apca = api.analyze.checkApca(fg, bg);
return (
<div className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">Accessibility Dashboard</h1>
<div className="grid md:grid-cols-2 gap-8 mb-8">
<div>
<label className="block mb-2 font-semibold">Foreground (Text)</label>
<input
type="color"
value={fgColor}
onChange={(e) => setFgColor(e.target.value)}
className="w-full h-16 rounded-lg"
/>
</div>
<div>
<label className="block mb-2 font-semibold">Background</label>
<input
type="color"
value={bgColor}
onChange={(e) => setBgColor(e.target.value)}
className="w-full h-16 rounded-lg"
/>
</div>
</div>
{/* Preview */}
<div
className="p-8 rounded-lg mb-8"
style={{ backgroundColor: bgColor, color: fgColor }}
>
<h2 className="text-2xl font-bold mb-4">Sample Heading</h2>
<p className="mb-4">
This is sample body text to preview the color combination.
Make sure it's readable and meets accessibility standards.
</p>
<button className="px-4 py-2 border rounded">Button</button>
</div>
{/* WCAG Results */}
<div className="grid md:grid-cols-2 gap-8">
<div className="p-6 rounded-lg border">
<h3 className="text-xl font-bold mb-4">WCAG 2.1</h3>
<div className="text-3xl font-bold mb-4">
{wcag.ratio.toFixed(2)}:1
</div>
<div className="space-y-2">
<ResultBadge label="Normal Text AA" pass={wcag.normalTextAA} />
<ResultBadge label="Normal Text AAA" pass={wcag.normalTextAAA} />
<ResultBadge label="Large Text AA" pass={wcag.largeTextAA} />
<ResultBadge label="Large Text AAA" pass={wcag.largeTextAAA} />
<ResultBadge label="UI Components AA" pass={wcag.uiComponentsAA} />
</div>
</div>
<div className="p-6 rounded-lg border">
<h3 className="text-xl font-bold mb-4">APCA</h3>
<div className="text-3xl font-bold mb-4">
Lc {Math.abs(apca.lc).toFixed(1)}
</div>
<div className="space-y-2">
<ResultBadge label="Body Text (75+)" pass={apca.meetsBodyText} />
<ResultBadge label="Large Text (60+)" pass={apca.meetsLargeText} />
<ResultBadge label="Non-text (45+)" pass={apca.meetsNonText} />
</div>
<div className="mt-4 text-sm text-gray-600">
Min font size: {apca.fontSizeNormal}px (normal) / {apca.fontSizeBold}px (bold)
</div>
</div>
</div>
</div>
);
}
function ResultBadge({ label, pass }: { label: string; pass: boolean }) {
return (
<div className={`flex items-center gap-2 text-sm ${pass ? 'text-green-600' : 'text-red-600'}`}>
<span className="font-bold">{pass ? '✓' : '✗'}</span>
<span>{label}</span>
</div>
);
}Performance Optimization
Lazy Loading
// app/tools/page.tsx
import dynamic from 'next/dynamic';
const ColorPicker = dynamic(() => import('@/components/ColorPicker'), {
ssr: false,
loading: () => <div>Loading color picker...</div>,
});
export default function ToolsPage() {
return <ColorPicker />;
}Preload WASM
// app/layout.tsx
import { headers } from 'next/headers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<link
rel="preload"
href="/color-assembly.wasm"
as="fetch"
type="application/wasm"
crossOrigin="anonymous"
/>
</head>
<body>{children}</body>
</html>
);
}Next Steps
- Check out React patterns
- Explore use cases
- View API reference