Generate accessible color palettes for Figma with automatic foreground color selection and WCAG contrast checking.
Features both a visual web UI for interactive editing and a CLI tool for automation.
v2.2 - Three-Tier Contrast Indicators (Current)
- π― AAA/AA/Fail System: Visual indicators now show three levels of WCAG compliance instead of just pass/fail
- π’ AAA (β₯7:1): Green indicators for contrast ratios that meet strict AAA standards
- π‘ AA-only (β₯4.5:1, <7:1): Yellow/amber indicators for ratios that meet AA but not AAA standards
- π΄ Fail (<4.5:1): Red indicators for ratios below minimum accessibility standards
- π·οΈ Level Labels: Contrast ratios display with "AAA" or "AA" badges for quick identification
- π¨ Smart Borders: Card borders reflect worst-case status (red overrides yellow, yellow overrides green)
- π Enhanced Summaries: Stats now show AAA count / AA count / Fail count (e.g., "24 / 8 / 6")
- π§ DRY Refactoring: Extracted shared contrast utilities reducing duplication by 75+ lines
- π¨ Status Token Added: New
--op-status-warningdesign token for consistent yellow/amber indicators
v2.1 - Code Refactoring & New Utilities
- π§ Major Refactoring: Cleaner, DRY codebase with ~200 lines reduced while adding utilities
- π¨ Unified Color Builder: New
createColorValue()utility eliminates duplication across 5+ locations - π Consolidated Scale Data: Optics scale data reduced from 6 objects (140 lines) to 1 structure (70 lines)
- β‘ Shared Utilities: New contrast failure collection and batch file export utilities
- π Improved Parsing: HSL input now handles both normalized (0-1) and percentage (0-100%) formats
- π¦ Better Patterns: Builder, Factory, and functional programming patterns throughout
- β 100% Test Coverage: All 169 tests passing with clean TypeScript compilation
v2.0 - Major UI Redesign
- π¨ Multiple Color Types: Manage Primary, Neutral, Secondary, Notice, Warning, Danger, Info, and custom color types in one workspace
- ποΈ Per-Type Controls: Independent H/S color pickers for each color type
- π¦ Unified Export: All enabled color types export to a single
optics-{mode}.tokens.jsonfile - π§© Collapsible Sections: Clean, organized interface with expand/collapse for each color type
- π Inline Summaries: Per-type contrast summaries show pass/fail counts at a glance
- βοΈπ Header Controls: Mode toggle and export moved to header for better space utilization
- π― Consistent Styling: Centralized color picker styles in shared module for uniform appearance
- β Custom Types: Add, rename, and delete custom color types as needed
- π¨ Optics Design System: UI now uses the Optics design system with semantic design tokens, including 4 color palettes (Primary, Neutral, Success, Danger) with 19 stops each, built-in light/dark mode support using CSS
light-dark(), and semantic tokens for spacing (x-small,medium,large), typography, borders, and interactions
- Quick Start - Get running in minutes (UI or CLI)
- Features - What this tool can do
- Installation - Setup instructions
- Interactive UI Usage - Visual editor guide
- CLI Usage - Command-line tool reference
- Understanding Contrast Reports - WCAG compliance checking
- Library Usage - Use as a Node.js package
- Advanced Utilities - New helper functions and patterns (Quick Reference)
- Export Formats - Figma, JSON, CSS, Tailwind
- Optics Scale Format - 19-stop semantic scale system
- Code Architecture - Refactoring improvements and best practices
- Testing - Comprehensive test suite
- Requirements & Dependencies
- Troubleshooting
cd optics-ui
npm install
npm run dev
# Open http://localhost:5173 in your browser
# Requires a modern browser (Chrome, Firefox, Safari, or Edge)Then use the visual editor to:
- Manage multiple color types (Primary, Neutral, Secondary, Notice, Warning, Danger, Info)
- Add custom color types with independent color controls
- Adjust luminosity for all 19 Optics stops with live contrast checking
- Configure Light and Dark modes independently
- Export all enabled color types to a single Figma tokens JSON file
# Install globally
npm install -g opticsthemebuilder
# Generate an Optics palette
optics generate "#3b82f6" --name "primary" --optics
# Or run locally without installing
cd OpticsThemeBuilder
npm install
npm run build
node dist/cli.js generate "#3b82f6" --name "primary" --optics- π₯οΈ Interactive Web UI: Svelte-based visual editor with multiple color type management, collapsible sections, live WCAG contrast checking, and real-time luminosity adjustments for all 19 Optics stops
- π¨ Multiple Color Types: Built-in support for Primary, Neutral, Secondary, Notice, Warning, Danger, and Info color scales, plus the ability to add custom color types
- ποΈ Per-Type Color Controls: Each color type has independent hue and saturation controls with visual color pickers
- π Optics Scale Format: 19 semantic stops with light/dark mode support (
plus-maxtominus-max) - π¨ Optics Design System: UI built with semantic design tokens - 4 color palettes (Primary, Neutral, Success, Danger) with 19 stops each, t-shirt sizing for spacing (
x-small,medium,large), typography tokens, and built-in light/dark theme support via CSSlight-dark()function - βΏ WCAG Compliant: Automatically calculates and validates contrast ratios (AA & AAA) with visual pass/fail indicators and per-type summaries
- π Dual Foregrounds: Provides light and dark foreground options for each background, plus alternative foregrounds
- π¦ Unified Figma Export: Export all enabled color types to a single Figma tokens JSON file per mode (light/dark)
- π CLI & Library: Use as a command-line tool or import into your Node.js project for automation
- π― Smart Color Scaling: Generates 16 perceptually-distributed color stops from any base color (CLI)
No global installation needed. Clone the repo and run:
git clone <repository-url>
cd OpticsThemeBuilder/optics-ui
npm install
npm run devThe UI uses the Optics design system with comprehensive documentation:
OPTICS_TOKEN_REFERENCE.md- Complete token reference guideQUICK_REFERENCE.md- Printable cheat sheetOPTICS_CSS_MIGRATION.md- Design system implementation details
Install globally for command-line usage:
npm install -g opticsthemebuilderOr use locally in a project:
npm install opticsthemebuilderManaging Color Types:
- Each color type (Primary, Neutral, Secondary, etc.) has its own collapsible section
- Toggle color types on/off using the switch in each section header
- Click section header to expand/collapse and view all 19 color stops
- Each section shows a contrast summary (e.g., "32 / 38" = 32 passing tests out of 38 total)
Color Controls:
- Each color type has independent H (Hue: 0-360Β°) and S (Saturation: 0-100%) controls in its header
- Use the color picker for visual selection or type values directly
- Changes apply to all stops within that color type
Adding Custom Color Types:
- Click "+ Add Custom Color Type" at the bottom
- Enter a name (e.g., "Accent", "Brand")
- Set the base color using the picker or H/S values
- Custom types can be renamed or deleted
Adjusting Luminosity: Each color stop card has three sliders:
- Background: Background color lightness (0-100%)
- On: Primary foreground lightness for text/icons
- On-Alt: Alternative foreground option
- Each displays live contrast ratios with pass/fail indicators (4.5:1 minimum for AA)
Light/Dark Modes:
- Toggle between Light and Dark modes using the sun/moon icons in the header
- Each mode has independent luminosity values for all stops
- Each color type shows its own contrast summary
Export:
- Click "Export for Figma" in the header
- Downloads a single
optics-{mode}.tokens.jsonfile (e.g.,optics-light.tokens.json) - The file contains all enabled color types with their current mode settings
- Import the file directly into Figma as Variables
Understanding Contrast Indicators:
- Green (AAA): Contrast ratio β₯7:1 - meets strict AAA standards, displays "AAA" badge
- Yellow (AA): Contrast ratio β₯4.5:1 but <7:1 - meets minimum AA standards, displays "AA" badge
- Red (Fail): Contrast ratio <4.5:1 - fails accessibility standards, no badge
- Card borders reflect the worst test result (red overrides yellow, yellow overrides green)
- Summary shows three counts: AAA / AA / Fail (e.g., "24 / 8 / 6" = 24 AAA, 8 AA-only, 6 failures)
Tips for achieving WCAG compliance:
- Light backgrounds (L>50%) need dark foregrounds (L<40%)
- Dark backgrounds (L<50%) need light foregrounds (L>60%)
- Aim for 40%+ lightness difference for AA, 60%+ for AAA
- Adjust sliders to turn yellow indicators green for better accessibility
- Use the contrast summary to track AAA/AA/Fail counts per color type
UI Design System: The interactive UI is built using the Optics design system it generates:
- 4 Color Palettes: Primary (blue), Neutral (gray), Success (green), Danger (red), each with 19 semantic stops
- Semantic Tokens: Surface, text, border, and interaction tokens for consistent styling
- Spacing Scale: T-shirt sizing from
3x-small(2px) to5x-large(64px) - Typography System: Font sizes, weights, and line heights for visual hierarchy
- Light/Dark Mode: Built-in theme support using CSS
light-dark()function - Documentation: See
optics-ui/OPTICS_TOKEN_REFERENCE.mdfor complete token reference
Generate a complete color palette from a single color:
# Standard format (16 stops)
optics generate "#3b82f6" --name "blue"
# Optics format (19 stops with light/dark mode)
optics generate "#3b82f6" --name "blue" --opticsThis creates:
blue-figma.json- Figma Variables format (standard palette)blue-light.tokens.json/blue-dark.tokens.json- Figma Variables format (Optics format, separate files for each mode)blue-contrast-report.txt- WCAG contrast analysis report
Optics Format - Use --optics flag for semantic naming (plus-max, base, minus-max) with built-in light/dark mode support. See OPTICS_FORMAT.md for details.
# Standard format with custom options
optics generate "#3b82f6" \
--name "primary" \
--stops 16 \
--output ./themes
# Optics format
optics generate "#3b82f6" \
--name "primary" \
--optics \
--output ./themes
# Generate Figma Variables for Light mode only (Optics)
optics generate "#3b82f6" \
--name "primary" \
--optics \
--mode light
# Generate both Light and Dark modes (default for Optics)
optics generate "#3b82f6" \
--name "primary" \
--optics \
--mode bothOptions:
--name, -n: Name for the palette (default: "palette")--stops, -s: Number of color stops (default: 16, range: 2-100, ignored with --optics)--output, -o: Output directory (default: "./output")--mode, -m: Mode for Figma export (Optics only) -light,dark, orboth(default: "both")--optics: Generate using Optics scale format (19 stops with light-dark mode)
Every CLI-generated palette includes a contrast report showing which color combinations meet WCAG standards:
## β οΈ FAILURES SUMMARY
Found 3 combinations that FAIL WCAG AA standard (4.5:1):
β Dark Mode β’ primary/minus-four (L:1%) β primary/minus-four-on (L:0%)
Background: #156af4 β Foreground: #00040a
Contrast: 4.30:1 (needs 4.5:1 minimum)
The report uses Optics naming (e.g., primary/minus-four-on) so you can immediately identify which stops need adjustment without matching hex codes.
The web UI provides real-time WCAG contrast validation with three-tier visual feedback:
- Three-tier indicators:
- π’ Green (AAA): β₯7:1 contrast ratio with "AAA" badge
- π‘ Yellow (AA): β₯4.5:1 but <7:1 with "AA" badge
- π΄ Red (Fail): <4.5:1, no badge
- Smart card borders: Overall border color reflects worst test (red > yellow > green)
- Live contrast ratios: Each color stop card displays contrast for both "on" and "on-alt" foregrounds
- Enhanced summaries: Shows AAA count / AA count / Fail count (e.g., "24 / 8 / 6")
- Summary dashboard: Section-level and global statistics with color-coded counts
- Mode-specific: Independent contrast checking for Light and Dark modes
- Instant feedback: Contrast recalculates as you adjust luminosity sliders
This allows you to fine-tune your palette interactively to meet WCAG AA standards or exceed them with AAA compliance.
optics analyze "#3b82f6" "#ffffff"
# Output: Contrast ratio, WCAG AA/AAA pass/failimport { generatePalette, exportToFigma, saveToFile } from 'opticsthemebuilder';
// Generate from hex color
const palette = generatePalette('#3b82f6', 'blue', 16);
console.log(palette.baseColor.hex); // "#3b82f6"
console.log(palette.stops.length); // 16
// HSL input now supports both formats
const palette1 = generatePalette({ h: 220, s: 0.8, l: 0.5 }, 'blue'); // Normalized (0-1)
const palette2 = generatePalette({ h: 220, s: 80, l: 50 }, 'blue'); // Percentages (0-100)
// Each stop contains:
palette.stops.forEach(stop => {
console.log({
index: stop.stop,
background: stop.background.hex,
lightForeground: stop.foregrounds.light.hex,
darkForeground: stop.foregrounds.dark.hex,
recommended: stop.recommendedForeground,
lightContrast: stop.foregrounds.light.contrast,
darkContrast: stop.foregrounds.dark.contrast,
wcagAA: stop.foregrounds.light.wcagAA
});
});import {
generatePalette,
exportToFigma,
exportToJSON,
exportToCSS,
exportToTailwind,
exportToDesignTokens,
exportAll
} from 'opticsthemebuilder';
const palette = generatePalette('#ff6b6b', 'red');
// Export to Figma Variables format (Light mode)
const figmaDataLight = exportToFigma(palette, 'Light');
console.log(JSON.stringify(figmaDataLight, null, 2));
// Export to Figma Variables format (Dark mode)
const figmaDataDark = exportToFigma(palette, 'Dark');
console.log(JSON.stringify(figmaDataDark, null, 2));
// Export to W3C Design Tokens format
const designTokens = exportToDesignTokens(palette);
// Export to JSON
const json = exportToJSON(palette);
// Export to CSS
const css = exportToCSS(palette);
// Export to Tailwind
const tailwind = exportToTailwind(palette);
// Export all formats to files
const files = exportAll(palette, './output');
console.log('Generated files:', files);import { getContrastRatio, meetsWCAG_AA, meetsWCAG_AAA } from 'opticsthemebuilder';
import { converter } from 'culori';
const toRgb = converter('rgb');
const background = toRgb('#3b82f6');
const foreground = toRgb('#ffffff');
const ratio = getContrastRatio(background, foreground);
console.log(`Contrast: ${ratio.toFixed(2)}:1`);
const passesAA = meetsWCAG_AA(ratio);
const passesAAA = meetsWCAG_AAA(ratio);
console.log('WCAG AA:', passesAA ? 'Pass' : 'Fail');
console.log('WCAG AAA:', passesAAA ? 'Pass' : 'Fail');New in v2.1: Create complete color values in one function call.
import { createColorValue } from 'opticsthemebuilder';
// Create a color with HSL, RGB, and hex all at once
const color = createColorValue(220, 80, 50); // h, s% (0-100), l% (0-100)
console.log(color.hsl); // { h: 220, s: 0.8, l: 0.5 }
console.log(color.rgb); // { r: 0.2, g: 0.4, b: 0.8 }
console.log(color.hex); // "#3366CC"Utilities for checking and collecting WCAG contrast failures:
import {
collectFailureIfNeeded,
createContrastFailure,
passesWCAG_AA
} from 'opticsthemebuilder';
// Collect failures automatically
const failure = collectFailureIfNeeded(
3.2, // contrast ratio
'Light', // mode
'primary/base', // stop identifier
{ hex: '#3366CC', hsl: { h: 220, s: 0.8, l: 0.5 } }, // background
{ hex: '#FFFFFF', hsl: { h: 0, s: 0, l: 1.0 } }, // foreground
'on' // foreground type
);
if (failure) {
console.log('WCAG AA failure detected:', failure);
}
// Or create failures manually
const customFailure = createContrastFailure(
'Dark', 'secondary/plus-two', bg, fg, 'on-alt', 2.8
);Export multiple files with consistent error handling:
import { exportFiles, FileExport } from 'opticsthemebuilder';
const files: FileExport[] = [
{
label: 'Figma Light Mode',
filename: 'primary-light.tokens.json',
content: JSON.stringify(lightModeData)
},
{
label: 'Figma Dark Mode',
filename: 'primary-dark.tokens.json',
content: JSON.stringify(darkModeData)
},
{
label: 'Contrast Report',
filename: 'contrast-report.txt',
content: reportContent
}
];
const exportedPaths = exportFiles('./output', files);
console.log('Exported:', exportedPaths);import {
parseColor, // Now handles both normalized and percentage HSL
createHsl, // Create HSL from percentage values
hslToRgb, // Convert HSL to RGB
rgbToHsl // Convert RGB to HSL
} from 'opticsthemebuilder';
// parseColor now auto-detects format
const color1 = parseColor({ h: 220, s: 0.8, l: 0.5 }); // Normalized
const color2 = parseColor({ h: 220, s: 80, l: 50 }); // Percentages
const color3 = parseColor('#3366CC'); // Hex string
const color4 = parseColor('hsl(220, 80%, 50%)'); // HSL stringHere's how the new utilities work together in a real-world scenario:
import {
createColorValue,
collectFailureIfNeeded,
exportFiles,
generateOpticsPalette
} from 'opticsthemebuilder';
// 1. Generate custom color stops using the unified builder
const customStops = [
createColorValue(220, 80, 95), // Very light
createColorValue(220, 80, 70), // Medium light
createColorValue(220, 80, 50), // Base
createColorValue(220, 80, 30), // Medium dark
createColorValue(220, 80, 15) // Very dark
];
// 2. Check contrast and collect failures
const failures = [];
customStops.forEach((bg, index) => {
const whiteFg = createColorValue(0, 0, 100);
const blackFg = createColorValue(0, 0, 0);
// Check white foreground
const whiteFailure = collectFailureIfNeeded(
calculateContrast(bg, whiteFg),
'Light',
`stop-${index}`,
bg,
whiteFg,
'white'
);
if (whiteFailure) failures.push(whiteFailure);
// Check black foreground
const blackFailure = collectFailureIfNeeded(
calculateContrast(bg, blackFg),
'Light',
`stop-${index}`,
bg,
blackFg,
'black'
);
if (blackFailure) failures.push(blackFailure);
});
// 3. Generate full Optics palette
const palette = generateOpticsPalette('#3366CC', 'primary');
// 4. Export everything in one batch
const files = [
{
label: 'Figma Light Mode',
filename: 'primary-light.tokens.json',
content: exportOpticsToFigma(palette, 'Light')
},
{
label: 'Figma Dark Mode',
filename: 'primary-dark.tokens.json',
content: exportOpticsToFigma(palette, 'Dark')
},
{
label: 'Contrast Report',
filename: 'contrast-report.txt',
content: generateReport(failures)
}
];
const paths = exportFiles('./output', files);
console.log('β
Exported:', paths);OpticsThemeBuilder accepts any valid CSS color format:
- Hex:
#3b82f6,#38f - RGB:
rgb(59, 130, 246),rgba(59, 130, 246, 0.5) - HSL:
hsl(217, 91%, 60%),hsla(217, 91%, 60%, 0.5) - HSL Object:
{ h: 217, s: 91, l: 60 }or{ h: 217, s: 0.91, l: 0.6 }(both normalized and percentage formats) - Named Colors:
blue,rebeccapurple,hotpink
Each palette contains 16 stops (by default) distributed from light to dark:
- Stop 0-3: Very light shades (backgrounds, subtle UI)
- Stop 4-7: Light to medium shades (hover states, secondary UI)
- Stop 8-11: Medium to dark shades (primary buttons, active states)
- Stop 12-15: Very dark shades (text, high contrast elements)
The generated .tokens.json files use Figma's native Variables import format:
-
Open Figma Variables Panel
- Menu β Libraries β Variables
-
Import Variables
- Click "..." menu β "Import variables"
- Select your
palette-light.tokens.jsonfile - Import to create Light mode
-
Import Dark Mode (optional)
- Select your
palette-dark.tokens.jsonfile - Import to the same collection
- Figma will automatically add the Dark mode
- Select your
The format follows Figma's exact specification with:
- Nested variable structure for organization
- Color values in sRGB space (0-1 range)
- Per-mode file structure
- Proper
$extensionsmetadata
See FIGMA_VARIABLES_FORMAT.md for complete format documentation.
For each background stop, you get two foreground options:
- Light Foreground: Near-white color with subtle hue matching the background
- Dark Foreground: Near-black color with subtle hue matching the background
The recommendedForeground field tells you which has better contrast.
- WCAG AA: Minimum 4.5:1 contrast for normal text, 3:1 for large text
- WCAG AAA: Minimum 7:1 contrast for normal text, 4.5:1 for large text
Import directly into Figma using the Variables panel:
How to Import:
- Open your Figma file
- Open the Variables panel (right sidebar)
- Click the "Import" button
- Select your
*-figma.jsonfile - All color variables will be imported into a new collection
The file structure matches Figma's Variables API format:
{
"variableCollections": {
"VariableCollectionId:blue": {
"name": "blue",
"modes": [...],
"variableIds": [...]
}
},
"variables": {
"VariableID:blue/0": {
"name": "blue/0",
"resolvedType": "COLOR",
"valuesByMode": {
"default": {
"r": 0.922,
"g": 0.943,
"b": 0.977,
"a": 1
}
},
"description": "Background color - Stop 0"
}
}
}Each palette creates:
- Background colors:
blue/0throughblue/15 - Light foregrounds:
blue/0/fg-lightthroughblue/15/fg-light - Dark foregrounds:
blue/0/fg-darkthroughblue/15/fg-dark
Complete palette data with all color information:
{
"name": "blue",
"baseColor": {
"hsl": { "h": 217, "s": 0.91, "l": 0.60 },
"rgb": { "r": 0.231, "g": 0.510, "b": 0.965 },
"hex": "#3b82f6"
},
"stops": [...]
}Ready-to-use CSS variables:
:root {
--blue-0: #ebf1f9;
--blue-0-rgb: 235, 241, 249;
--blue-0-hsl: 217, 55%, 95%;
--blue-0-fg-light: #fafafa;
--blue-0-fg-dark: #131416;
--blue-0-fg: #131416; /* recommended */
}Add to your tailwind.config.js:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
'blue': {
0: '#ebf1f9',
1: '#e7eef9',
// ... etc
}
}
}
}
}Generate consistent color scales for your entire design system:
optics generate "#3b82f6" --name "primary" --output ./design-tokens
optics generate "#10b981" --name "success" --output ./design-tokens
optics generate "#ef4444" --name "error" --output ./design-tokens
optics generate "#f59e0b" --name "warning" --output ./design-tokensQuickly check if your color combinations meet WCAG standards:
optics analyze "#your-brand-blue" "#ffffff"Generate palettes and import them directly into Figma:
# Generate Figma-compatible file
optics generate "#3b82f6" --name "primary"
# Import the *-figma.json file in Figma:
# 1. Open Variables panel
# 2. Click "Import" button
# 3. Select primary-figma.json
# 4. Your color system is now ready to use!All variables include descriptions with contrast ratios and WCAG compliance information.
Generate complementary light and dark mode palettes by using different base lightness values.
Colors are distributed using an easing function to create more stops in the mid-range where the human eye is most sensitive to changes.
- Very light colors (L > 90%): Saturation is reduced to avoid garish pastels
- Very dark colors (L < 20%): Saturation is reduced to maintain depth
- Mid-range colors: Saturation is maintained or slightly boosted
- Light foregrounds: ~98% lightness, minimal saturation
- Dark foregrounds: ~8% lightness, slight saturation for warmth
- Both maintain the base color's hue for visual coherence
Full TypeScript definitions included:
import { ColorPalette, ColorStop, HSLColor } from 'opticsthemebuilder';
const palette: ColorPalette = generatePalette('#3b82f6');
const stop: ColorStop = palette.stops[0];
const hsl: HSLColor = stop.background.hsl;CLI & Library:
- Node.js 14+, NPM/Yarn
- Dependencies:
culori(colors),commander(CLI)
Interactive UI:
- Node.js 14+, NPM/Yarn, modern browser
- Dependencies:
svelte,culori,vite,typescript
The Optics format provides an alternative color scale system with semantic naming and built-in light/dark mode support:
- 19 semantic stops:
plus-max,plus-eight, ...,base, ...,minus-eight,minus-max - Light/Dark modes: Automatic theme switching with CSS
light-dark()function - Three colors per stop: background, "on" (primary foreground), "on-alt" (alternative foreground)
- F-stop naming: Photography-inspired names for intuitive luminosity control
The Interactive UI uses the Optics format: The web interface is built with the Optics design system, featuring semantic design tokens for colors, spacing (t-shirt sizing: x-small, medium, large), typography, and more. See optics-ui/OPTICS_TOKEN_REFERENCE.md for the complete implementation.
# Generate Optics palette
optics generate "#3b82f6" --name "primary" --optics
# Use in CSS
:root {
--op-color-primary-base: light-dark(hsl(217 91% 40%), hsl(217 91% 38%));
--op-color-primary-on-base: light-dark(hsl(217 91% 100%), hsl(217 91% 100%));
}import { generateOpticsPalette, exportOpticsToCSS } from 'opticsthemebuilder';
const palette = generateOpticsPalette('#3b82f6', 'primary');
const css = exportOpticsToCSS(palette);π Full Documentation: See OPTICS_FORMAT.md for complete guide, examples, and API reference.
π‘ Examples: Run node example-optics.js to see comprehensive usage examples.
β Fully compatible with Figma Variables import
The --optics format generates Figma-compatible exports that match the Design Tokens structure expected by Figma's Variables plugin.
-
Generate your palette:
optics generate "#2a659f" --name "primary" --optics --format figma
-
In Figma:
- Open the Variables panel (right sidebar)
- Click the book icon (Import/Export)
- Select Import variables
- Choose your
*-optics-figma.jsonfile - Figma imports as a collection with Light/Dark modes
-
Use in designs:
- Select any frame or element
- In color picker, click the π icon
- Choose from your imported variables
- Switch between Light/Dark modes to see values update
- Collection: Named after your palette (e.g., "Primary Colors")
- Modes: Light and Dark (114 tokens total)
- Tokens per mode: 57 variables
- 19 background colors (
*-bg) - 19 primary foregrounds (
*-on) - 19 alternative foregrounds (
*-on-alt)
- 19 background colors (
- Contrast info: Each foreground includes contrast ratio in description
The export format has been validated against Figma plugin exports:
- β Matches Figma's Design Tokens structure
- β
Includes all required fields (
$codeSyntax,$scopes,$type,$value) - β Supports multi-mode theming (Light/Dark)
- β Compatible with Figma Variables import/export
π See FORMAT_VALIDATION.md for detailed format comparison and validation.
The codebase has undergone significant refactoring to improve maintainability, reduce duplication, and establish better patterns. See REFACTORING.md for complete details.
1. Unified Color Builder Pattern
- Single
createColorValue()function replaces 5+ duplicate implementations - Generates HSL, RGB, and hex values in one call
- Eliminates manual conversion chains
2. Consolidated Data Structures
- Optics scale data: 6 separate objects (140 lines) β 1 structured array (70 lines)
- Related data kept together logically
- Self-documenting configuration objects
3. Functional Programming Patterns
- Declarative
flatMap()instead of imperative loops - Immutable data transformations
- Cleaner, more readable code flow
4. Shared Utilities
- Contrast failure collection extracted to reusable functions
- Batch file export with consistent error handling
- Display logic separated from business logic
5. Contrast Level Utilities (v2.2)
getContrastLevel()- Single source of truth for AAA/AA/Fail determinationgetContrastLabel()- Consistent badge display ("AAA", "AA", "")getOverallStatus()- Priority-based status calculation (fail > aa > aaa)calculateContrastStats()- DRY stats aggregation for all components- Eliminates 75+ lines of duplicate contrast logic
6. Enhanced Type Safety
- Comprehensive TypeScript interfaces
ContrastLeveltype for three-tier system- Better IntelliSense support
- Compile-time error detection
- Lines Reduced: ~275 lines removed while adding utilities (v2.1: 200, v2.2: 75)
- Test Coverage: 100% maintained (169 tests passing)
- Duplication: Major patterns eliminated across 8+ locations
- Build Status: Clean TypeScript compilation with zero errors
- Component Efficiency: ColorStopCard reduced from 40 to 9 lines of template markup
- Builder Pattern:
createColorValue(),createFigmaColorValue() - Factory Pattern: Unified color creation with consistent output
- Single Responsibility: Each utility has one clear purpose
- DRY Principle: Code duplication eliminated throughout
src/
βββ generator.ts # Standard palette generation
βββ optics-generator.ts # Optics scale generation (consolidated data)
βββ color-utils.ts # Unified color builder & utilities
βββ contrast.ts # WCAG contrast calculations
βββ contrast-report-utils.ts # Shared reporting utilities
βββ exporter.ts # Standard export formats
βββ optics-exporter.ts # Optics export formats
βββ figma-utils.ts # Figma token creation
βββ file-utils.ts # File I/O and batch export
βββ console-utils.ts # CLI output formatting
optics-ui/src/lib/
βββ utils/
β βββ colors.ts # Color utilities + contrast level functions
β βββ contrast-stats.ts # Shared contrast statistics calculation
βββ components/
β βββ Summary.svelte # Uses calculateContrastStats()
β βββ ColorTypeSection.svelte # Uses calculateContrastStats()
β βββ ColorStopCard.svelte # Uses getContrastLevel() utilities
βββ cli.ts # Command-line interface
βββ index.ts # Public API exports
βββ types.ts # TypeScript type definitions
- Consistent Color Handling: All color operations use the unified builder
- Declarative Style: Functional array methods over imperative loops
- Shared Logic: Common patterns extracted to utilities
- Type Safety: Full TypeScript coverage with no
anytypes - Test Coverage: Every utility function has comprehensive tests
The refactored architecture makes it easy to:
- Add new color scale formats
- Implement custom export formats
- Create plugin systems
- Extend WCAG validation rules
- Add performance optimizations
OpticsThemeBuilder includes a comprehensive test suite ensuring code quality and reliability.
Test Suites: 6 passed, 6 total
Tests: 169 passed, 169 total
Coverage: 100% (all tested modules)
Execution: ~1.5-2 seconds
β Color Utilities (32 tests)
- Color parsing (hex, RGB, HSL, named colors)
- Color space conversions (HSL β RGB)
- Type conversions and edge cases
β Contrast Calculations (24 tests)
- WCAG 2.0 compliant luminance calculations
- Contrast ratios (1:1 to 21:1)
- AA/AAA compliance checking
β File Operations (21 tests)
- File I/O with directory creation
- JSON formatting
- Special character handling
β Figma Format (31 tests)
- Color conversion to Figma sRGB
- Token creation and validation
- Variable ID generation
β Contrast Reports (28 tests)
- Report generation and formatting
- Failure summaries
- WCAG level determination
β Palette Generation (43 tests)
- Color stop creation
- Lightness distribution
- Saturation adjustment
- Foreground selection
# Run all tests
npm test
# Run with coverage report
npm run test:coverage
# Watch mode (for development)
npm run test:watch
# Run specific test suite
npm test -- contrast.test
# Verbose output
npm test -- --verboseAll core modules have 100% coverage:
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
color-utils.ts | 100 | 100 | 100 | 100 |
contrast.ts | 100 | 100 | 100 | 100 |
file-utils.ts | 100 | 100 | 100 | 100 |
figma-utils.ts | 100 | 100 | 100 | 100 |
contrast-report-utils.ts | 100 | 100 | 100 | 100 |
generator.ts | 100 | 100 | 100 | 100 |
For detailed information about the test suite:
- TESTING.md - Comprehensive test documentation
- TEST_SUMMARY.md - Test suite overview
- tests/README.md - Quick testing reference
The test suite ensures:
- β Accurate color calculations - WCAG compliant contrast ratios
- β Reliable file operations - Safe I/O with edge case handling
- β Valid Figma exports - Correct token structure and format
- β Consistent palette generation - Deterministic color scales
- β No regressions - All changes validated automatically
ISC
UI Issues:
- Sliders not responding? Refresh page or restart dev server (
npm run devinoptics-ui/) - Can't achieve 4.5:1 contrast? Increase lightness difference between bg/fg (40%+ recommended)
- Export downloads one file per color type? Make sure you're using the latest version (now exports all enabled types to one file)
- Color type not showing in export? Check that it's toggled on (switch in section header)
- Color picker values not updating? Try using the number inputs directly or refresh the page
CLI Issues:
- Type errors? Run
npm run checkto validate TypeScript - Colors look wrong? Verify H (0-360) and S (0-100) are in valid ranges
Architecture Notes:
- Color picker styles are centralized in
src/lib/styles/shared.module.cssfor consistency - Each color type is managed independently with its own H/S values
- Export format combines all enabled types into a single JSON structure per mode
Contributions welcome! Please open an issue or PR.
Before contributing, please review:
- REFACTORING.md - Architecture decisions and code patterns
- UTILITIES_REFERENCE.md - Quick reference for new utilities
- Code Architecture - Design patterns and best practices
- Test suite (
npm test) - Ensure all tests pass before submitting
Code Style:
- Follow existing patterns (Builder, Factory, DRY principles)
- Use TypeScript with full type coverage
- Add tests for new functionality
- Keep utilities focused and reusable
Built with β€οΈ for designers and developers who care about accessibility.
Pro Tips:
- Start with medium saturation (70-90%) for best results
- For CLI: Use medium lightness (40-60%) base colors to generate full tint/shade ranges
- Achieving WCAG AA requires 40%+ lightness difference between backgrounds and foregrounds