Claude Skills Guide

AI Color Picker Chrome Extension: A Developer Guide

Color selection has evolved beyond simple hex code pickers. Modern AI-powered color picker extensions bring intelligent color suggestions, palette generation, and accessibility analysis directly into your browser workflow. This guide covers the technical implementation and practical applications for developers and power users building or using these tools.

How AI Color Pickers Differ from Traditional Tools

Traditional color pickers give you a visual interface to select colors—they capture pixel data from the screen and convert it to hex, RGB, or HSL values. AI color pickers extend this core functionality by analyzing color relationships, suggesting complementary shades, checking contrast ratios for accessibility compliance, and generating harmonious palettes based on context.

The technical foundation involves three components: screen capture for color extraction, machine learning models for color analysis, and a user interface for displaying results and suggestions.

Setting Up Your Extension

A basic AI color picker extension requires Manifest V3 configuration with specific permissions for screen capture and storage:

{
  "manifest_version": 3,
  "name": "AI Color Picker",
  "version": "1.0.0",
  "permissions": [
    "activeTab",
    "scripting",
    "storage"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  }
}

The key difference from traditional color pickers is the inclusion of AI processing logic, which can run locally using WebAssembly models or connect to external AI services for more sophisticated analysis.

Implementing Color Capture

The core functionality starts with capturing colors from the webpage. Use the chrome.scripting.executeScript API to inject a color extraction script:

// background.js
chrome.action.onClicked.addListener(async (tab) => {
  const results = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    function: extractColorsFromPage
  });
  
  const colors = results[0].result;
  processColorsWithAI(colors);
});

function extractColorsFromPage() {
  const colors = new Map();
  
  // Get colors from computed styles
  const elements = document.querySelectorAll('*');
  elements.forEach(el => {
    const style = window.getComputedStyle(el);
    const bgColor = style.backgroundColor;
    const textColor = style.color;
    
    if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)') {
      colors.set(bgColor, (colors.get(bgColor) || 0) + 1);
    }
    if (textColor) {
      colors.set(textColor, (colors.get(textColor) || 0) + 1);
    }
  });
  
  // Sort by frequency and return top colors
  return Array.from(colors.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 20)
    .map(([color]) => color);
}

This script extracts the dominant colors from a page, giving your AI model context about the current design palette.

Building the Color Analysis Engine

The AI component analyzes extracted colors to provide meaningful suggestions. Here’s a practical implementation using local processing:

// color-analyzer.js
class ColorAnalyzer {
  constructor() {
    this.contrastThreshold = 4.5; // WCAG AA for normal text
  }

  // Convert hex to RGB
  hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    } : null;
  }

  // Calculate relative luminance
  getLuminance(r, g, b) {
    const [rs, gs, bs] = [r, g, b].map(c => {
      c = c / 255;
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
  }

  // Check contrast ratio between two colors
  getContrastRatio(color1, color2) {
    const rgb1 = this.hexToRgb(color1);
    const rgb2 = this.hexToRgb(color2);
    
    if (!rgb1 || !rgb2) return 0;
    
    const l1 = this.getLuminance(rgb1.r, rgb1.g, rgb1.b);
    const l2 = this.getLuminance(rgb2.r, rgb2.g, rgb2.b);
    
    const lighter = Math.max(l1, l2);
    const darker = Math.min(l1, l2);
    
    return (lighter + 0.05) / (darker + 0.05);
  }

  // Generate complementary colors
  getComplementary(hex) {
    const rgb = this.hexToRgb(hex);
    if (!rgb) return null;
    
    return this.rgbToHex(
      255 - rgb.r,
      255 - rgb.g,
      255 - rgb.b
    );
  }

  // Generate analogous colors
  getAnalogous(hex) {
    const hsl = this.hexToHsl(hex);
    return [
      this.hslToHex((hsl.h + 30) % 360, hsl.s, hsl.l),
      this.hslToHex((hsl.h - 30 + 360) % 360, hsl.s, hsl.l)
    ];
  }

  // Analyze color for accessibility
  analyzeAccessibility(foreground, background) {
    const ratio = this.getContrastRatio(foreground, background);
    return {
      ratio: ratio.toFixed(2),
      passesAA: ratio >= 4.5,
      passesAAA: ratio >= 7.0,
      suggestion: ratio < 4.5 ? 
        'Consider using a darker foreground or lighter background' : 
        'Passes WCAG AA requirements'
    };
  }
}

This analyzer provides core functionality that power users need: contrast checking, palette generation, and accessibility validation.

Color Format Conversion Utilities

Supporting multiple color formats requires conversion utilities. Here’s a comprehensive set covering HEX, RGB, and HSL:

// color-utils.js - Color Conversion Functions
const ColorUtils = {
  hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    } : null;
  },

  rgbToHsl(r, g, b) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;

    if (max === min) {
      h = s = 0;
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
        case g: h = ((b - r) / d + 2) / 6; break;
        case b: h = ((r - g) / d + 4) / 6; break;
      }
    }
    return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
  },

  generatePalette(baseColor, count = 5) {
    const rgb = this.hexToRgb(baseColor);
    if (!rgb) return [];

    const hsl = this.rgbToHsl(rgb.r, rgb.g, rgb.b);
    const palette = [];

    for (let i = 0; i < count; i++) {
      const newL = Math.max(10, Math.min(90, hsl.l + (i - 2) * 15));
      palette.push(`hsl(${hsl.h}, ${hsl.s}%, ${newL}%)`);
    }
    return palette;
  }
};

Storing Color History

Persisting color history improves workflow efficiency. The Chrome Storage API handles this cleanly:

// storage.js - Color History Management
const ColorHistory = {
  STORAGE_KEY: 'color_picker_history',
  MAX_HISTORY: 20,

  async addColor(color) {
    const history = await this.getHistory();
    const filtered = history.filter(c => c !== color);
    filtered.unshift(color);

    const trimmed = filtered.slice(0, this.MAX_HISTORY);

    return new Promise((resolve) => {
      chrome.storage.local.set({ [this.STORAGE_KEY]: trimmed }, resolve);
    });
  },

  async getHistory() {
    return new Promise((resolve) => {
      chrome.storage.local.get(this.STORAGE_KEY, (result) => {
        resolve(result[this.STORAGE_KEY] || []);
      });
    });
  }
};

The implementation limits history to 20 colors to prevent unbounded storage growth while maintaining useful recent selections.

Creating the User Interface

The popup interface should provide quick access to captured colors and AI suggestions:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 320px; padding: 16px; font-family: system-ui; }
    .color-swatch {
      width: 40px; height: 40px;
      border-radius: 8px;
      display: inline-block;
      margin: 4px;
      cursor: pointer;
      border: 2px solid transparent;
    }
    .color-swatch:hover { transform: scale(1.1); }
    .color-swatch.selected { border-color: #0066cc; }
    .color-info { margin: 12px 0; padding: 12px; background: #f5f5f5; border-radius: 8px; }
    .color-value { font-family: monospace; font-size: 14px; }
    .contrast-result { margin-top: 8px; padding: 8px; border-radius: 4px; }
    .pass { background: #d4edda; color: #155724; }
    .fail { background: #f8d7da; color: #721c24; }
  </style>
</head>
<body>
  <h3>AI Color Picker</h3>
  <div id="colors"></div>
  <div id="analysis"></div>
  <script src="popup.js"></script>
</body>
</html>
// popup.js
const analyzer = new ColorAnalyzer();

document.addEventListener('DOMContentLoaded', async () => {
  // Get colors from storage (set by background script)
  const result = await chrome.storage.local.get('extractedColors');
  const colors = result.extractedColors || [];
  
  const container = document.getElementById('colors');
  
  colors.slice(0, 12).forEach((color, index) => {
    const swatch = document.createElement('div');
    swatch.className = 'color-swatch';
    swatch.style.backgroundColor = color;
    swatch.dataset.color = color;
    
    swatch.addEventListener('click', () => {
      selectColor(color);
      document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
      swatch.classList.add('selected');
    });
    
    container.appendChild(swatch);
  });
});

function selectColor(color) {
  const analysis = document.getElementById('analysis');
  const rgb = analyzer.hexToRgb(color);
  
  // Generate palette suggestions
  const complementary = analyzer.getComplementary(color);
  const analogous = analyzer.getAnalogous(color);
  
  analysis.innerHTML = `
    <div class="color-info">
      <div class="color-value">HEX: ${color}</div>
      <div class="color-value">RGB: ${rgb.r}, ${rgb.g}, ${rgb.b}</div>
    </div>
    <h4>Suggested Palette</h4>
    <div class="color-swatch" style="background-color: ${complementary}" 
         title="Complementary"></div>
    ${analogous.map(c => `<div class="color-swatch" style="background-color: ${c}" title="Analogous"></div>`).join('')}
  `;
}

Advanced Features for Power Users

Beyond basic color picking, advanced extensions offer these capabilities:

Color naming — Use AI to describe colors in human-readable terms. Train a small model to recognize thousands of color names from CSS color keywords plus extended palettes.

Palette extraction from images — Extract dominant colors from any image on the page using color quantization algorithms:

// Extract palette from images on page
function extractFromImages() {
  const images = document.querySelectorAll('img');
  const palettes = [];
  
  images.forEach(img => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    // Sample at small size for performance
    canvas.width = 50;
    canvas.height = 50;
    
    if (img.complete) {
      ctx.drawImage(img, 0, 0, 50, 50);
      const data = ctx.getImageData(0, 0, 50, 50).data;
      
      // Simple color quantization
      const colorCounts = {};
      for (let i = 0; i < data.length; i += 4) {
        const r = Math.round(data[i] / 32) * 32;
        const g = Math.round(data[i + 1] / 32) * 32;
        const b = Math.round(data[i + 2] / 32) * 32;
        const key = `rgb(${r},${g},${b})`;
        colorCounts[key] = (colorCounts[key] || 0) + 1;
      }
      
      palettes.push(colorCounts);
    }
  });
  
  return palettes;
}

Export capabilities — Allow users to export palettes in multiple formats:

function exportPalette(colors, format) {
  switch (format) {
    case 'css':
      return `:root {\n${colors.map((c, i) => `  --color-${i + 1}: ${c};`).join('\n')}\n}`;
    case 'json':
      return JSON.stringify(colors.map(c => ({ hex: c })), null, 2);
    case 'scss':
      return colors.map((c, i) => `$color-${i + 1}: ${c};`).join('\n');
    default:
      return colors.join(', ');
  }
}

A polished popup requires thoughtful CSS and a clean input handler. Here’s a practical baseline:

/* popup.css - Color Picker Styles */
.color-picker-container {
  width: 280px;
  padding: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.color-preview {
  width: 100%;
  height: 60px;
  border-radius: 8px;
  border: 1px solid #ddd;
  margin-bottom: 16px;
  transition: background-color 0.2s ease;
}

.color-input-wrapper {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

.color-input-wrapper input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 6px;
  font-family: 'Monaco', 'Consolas', monospace;
  font-size: 14px;
}

.copy-button {
  padding: 8px 16px;
  background: #0066cc;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 500;
}

.copy-button:hover {
  background: #0052a3;
}

Pair the styles with a ColorPicker class for real-time preview updates:

// popup.js - Color Picker Logic
class ColorPicker {
  constructor(inputElement, previewElement) {
    this.input = inputElement;
    this.preview = previewElement;
    this.init();
  }

  init() {
    this.input.addEventListener('input', (e) => this.updatePreview(e.target.value));
    this.updatePreview(this.input.value);
  }

  updatePreview(color) {
    this.preview.style.backgroundColor = color;
    this.input.value = color.toUpperCase();
  }

  getColor() {
    return this.input.value;
  }
}

document.addEventListener('DOMContentLoaded', () => {
  const picker = new ColorPicker(
    document.getElementById('color-input'),
    document.getElementById('color-preview')
  );
});

Eyedropper API: An Alternative Capture Method

For extensions that need users to sample any pixel on screen—not just computed CSS values—Chrome’s built-in Eyedropper API is a cleaner alternative to injecting content scripts:

async function startEyedropper() {
  if (!window.EyeDropper) {
    console.warn('EyeDropper API not supported in this browser');
    return null;
  }

  const eyedropper = new EyeDropper();

  try {
    const result = await eyedropper.open();
    return result.sRGBHex; // e.g. "#3b82f6"
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error('Eyedropper error:', error);
    }
    return null;
  }
}

Use chrome.scripting-based extraction (see above) when you want to automatically harvest all dominant colors from a page without user interaction. Use the Eyedropper API when you want the user to deliberately sample a single pixel from anywhere on screen. Both approaches pair well with the ColorAnalyzer class.

Content Script Integration

For extensions that pick colors directly from web pages, a content script enables click-to-capture:

// content-script.js - Page Color Picker
document.addEventListener('click', (e) => {
  if (e.target.dataset.colorPickerActive === 'true') {
    const computedStyle = window.getComputedStyle(e.target);
    const bgColor = computedStyle.backgroundColor;

    chrome.runtime.sendMessage({
      type: 'COLOR_PICKED',
      color: bgColor
    });
  }
});

This pattern enables users to click elements on any webpage and capture their colors directly into the extension’s color picker interface.

Accessibility Considerations

Accessible color picker design ensures usability across different abilities. Implement keyboard navigation throughout the picker interface, provide sufficient color contrast in your own UI, and offer non-visual color information through ARIA labels:

<input
  type="color"
  id="color-input"
  aria-label="Select color"
  aria-describedby="color-description"
>
<span id="color-description" class="sr-only">
  Current color value in hex format
</span>

Testing and Deployment

Load your extension in Chrome through chrome://extensions/ with Developer mode enabled. Test the color picker across different websites, including:

Use Chrome’s devtools to debug content scripts and monitor API calls. Check the background service worker console for errors in AI processing.

Conclusion

Building an AI color picker Chrome extension combines traditional color extraction with intelligent analysis. The most useful features for developers and power users include accessibility contrast checking, palette generation, and export in multiple formats. Start with solid color extraction, add accessibility validation, then layer on AI-powered suggestions.

Focus on performance—color analysis should feel instant. Cache results when possible and use Web Workers for heavy computation. Lazy-load color conversion utilities, use event delegation instead of individual listeners, and cache computed colors to avoid redundant calculations. The popup interface loads synchronously, so minimizing initial JavaScript payload improves perceived performance. Users will appreciate having instant access to color information without waiting for network requests.

The extension ecosystem rewards focused tools that solve specific problems well. A color picker that excels at accessibility checking and palette generation will find a dedicated audience among developers who care about design consistency and WCAG compliance.


Built by theluckystrike — More at zovo.one