CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tinycolor2

Fast color parsing and manipulation library with comprehensive conversion and accessibility features

Pending
Overview
Eval results
Files

accessibility.mddocs/

Accessibility and Readability

Static methods for calculating color contrast ratios and determining readability according to WCAG (Web Content Accessibility Guidelines). These functions help ensure your color choices meet accessibility standards for users with visual impairments.

Capabilities

Contrast Ratio Calculation

Calculate Readability

Calculates the contrast ratio between two colors using WCAG formulas.

/**
 * Calculates the contrast ratio between two colors
 * Uses WCAG formula: (L1 + 0.05) / (L2 + 0.05) where L1 is lighter, L2 is darker
 * @param color1 - First color (any valid tinycolor input)
 * @param color2 - Second color (any valid tinycolor input)
 * @returns number representing contrast ratio (1-21, where 21 is maximum contrast)
 */
tinycolor.readability(color1: any, color2: any): number;

Usage Examples:

import tinycolor from "tinycolor2";

// Calculate contrast ratios
const whiteBlack = tinycolor.readability("white", "black");
const whiteGray = tinycolor.readability("#ffffff", "#808080");
const blueWhite = tinycolor.readability("#0066cc", "white");

console.log("White/Black contrast:", whiteBlack);    // 21 (maximum contrast)
console.log("White/Gray contrast:", whiteGray);      // ~2.28
console.log("Blue/White contrast:", blueWhite);      // ~4.89

// Check various background/text combinations
const backgrounds = ["#ffffff", "#f8f9fa", "#e9ecef", "#343a40", "#000000"];
const textColors = ["#000000", "#495057", "#6c757d", "#ffffff"];

backgrounds.forEach(bg => {
  console.log(`Background: ${bg}`);
  textColors.forEach(text => {
    const ratio = tinycolor.readability(bg, text);
    console.log(`  Text ${text}: ${ratio.toFixed(2)}`);
  });
});

WCAG Compliance Testing

Test Readability Standards

Tests if two colors meet WCAG readability guidelines.

/**
 * Tests if color combination meets WCAG guidelines
 * @param color1 - First color (any valid tinycolor input)
 * @param color2 - Second color (any valid tinycolor input)  
 * @param wcag2 - WCAG parameters object (optional)
 * @returns boolean indicating if combination meets standards
 */
tinycolor.isReadable(color1: any, color2: any, wcag2?: {level?: "AA" | "AAA", size?: "small" | "large"}): boolean;

WCAG Standards:

  • AA Large Text: Contrast ratio ≥ 3:1
  • AA Normal Text: Contrast ratio ≥ 4.5:1
  • AAA Large Text: Contrast ratio ≥ 4.5:1
  • AAA Normal Text: Contrast ratio ≥ 7:1

Usage Examples:

// Test with default settings (AA level, small text)
const isReadable1 = tinycolor.isReadable("#000000", "#ffffff");
console.log("Black on white:", isReadable1); // true

const isReadable2 = tinycolor.isReadable("#777777", "#ffffff");  
console.log("Gray on white:", isReadable2); // false (insufficient contrast)

// Test with specific WCAG levels
const combinations = [
  { bg: "#ffffff", text: "#000000" },
  { bg: "#ffffff", text: "#666666" },
  { bg: "#ffffff", text: "#999999" },
  { bg: "#2c3e50", text: "#ffffff" },
  { bg: "#3498db", text: "#ffffff" }
];

combinations.forEach(combo => {
  const aaSmall = tinycolor.isReadable(combo.bg, combo.text, {level: "AA", size: "small"});
  const aaLarge = tinycolor.isReadable(combo.bg, combo.text, {level: "AA", size: "large"});
  const aaaSmall = tinycolor.isReadable(combo.bg, combo.text, {level: "AAA", size: "small"});
  const aaaLarge = tinycolor.isReadable(combo.bg, combo.text, {level: "AAA", size: "large"});
  
  console.log(`${combo.text} on ${combo.bg}:`);
  console.log(`  AA Small: ${aaSmall}, AA Large: ${aaLarge}`);
  console.log(`  AAA Small: ${aaaSmall}, AAA Large: ${aaaLarge}`);
});

// Validate user interface colors
function validateUIColors(backgroundColor, textColor, level = "AA") {
  const tests = {
    smallText: tinycolor.isReadable(backgroundColor, textColor, {level, size: "small"}),
    largeText: tinycolor.isReadable(backgroundColor, textColor, {level, size: "large"}),
    ratio: tinycolor.readability(backgroundColor, textColor)
  };
  
  return {
    passes: tests.smallText,
    passesLarge: tests.largeText,
    ratio: tests.ratio,
    level: level
  };
}

Find Most Readable Color

Find Optimal Text Color

Finds the most readable color from a list of options for a given background.

/**
 * Finds the most readable color from a list for the given background
 * @param baseColor - Background color (any valid tinycolor input)
 * @param colorList - Array of potential text colors
 * @param args - Optional arguments object
 * @returns tinycolor instance of the most readable color
 */
tinycolor.mostReadable(baseColor: any, colorList: any[], args?: {includeFallbackColors?: boolean, level?: "AA" | "AAA", size?: "small" | "large"}): tinycolor;

Usage Examples:

// Find best text color for various backgrounds
const textOptions = ["#000000", "#333333", "#666666", "#999999", "#ffffff"];

const darkBg = "#2c3e50";
const lightBg = "#ecf0f1";
const colorfulBg = "#e74c3c";

const bestForDark = tinycolor.mostReadable(darkBg, textOptions);
const bestForLight = tinycolor.mostReadable(lightBg, textOptions);
const bestForColorful = tinycolor.mostReadable(colorfulBg, textOptions);

console.log("Best text for dark background:", bestForDark.toHexString());
console.log("Best text for light background:", bestForLight.toHexString());
console.log("Best text for colorful background:", bestForColorful.toHexString());

// With WCAG level requirements
const bestAAA = tinycolor.mostReadable("#3498db", ["#ffffff", "#000000"], {
  level: "AAA",
  size: "small"
});

// With fallback colors (includes black and white if needed)
const bestWithFallback = tinycolor.mostReadable("#ff6b6b", ["#ffcc02", "#06ffa5"], {
  includeFallbackColors: true,
  level: "AA"
});

// Smart text color selection
function getOptimalTextColor(backgroundColor, preferredColors = ["#000000", "#ffffff"]) {
  const bgColor = tinycolor(backgroundColor);
  
  // Start with preferred colors
  let bestColor = tinycolor.mostReadable(bgColor, preferredColors);
  
  // If no preferred color works, expand options
  if (!tinycolor.isReadable(bgColor, bestColor)) {
    const expandedOptions = [
      "#000000", "#333333", "#666666", "#999999", "#cccccc", "#ffffff"
    ];
    bestColor = tinycolor.mostReadable(bgColor, expandedOptions, {
      includeFallbackColors: true
    });
  }
  
  return bestColor.toHexString();
}

Accessibility Workflows

Comprehensive Accessibility Analysis

function analyzeColorAccessibility(backgroundColor, textColor) {
  const bgColor = tinycolor(backgroundColor);
  const txtColor = tinycolor(textColor);
  
  if (!bgColor.isValid() || !txtColor.isValid()) {
    return { error: "Invalid color input" };
  }
  
  const ratio = tinycolor.readability(bgColor, txtColor);
  
  const compliance = {
    "AA-small": ratio >= 4.5,
    "AA-large": ratio >= 3.0,
    "AAA-small": ratio >= 7.0,
    "AAA-large": ratio >= 4.5
  };
  
  const suggestions = [];
  
  if (!compliance["AA-small"]) {
    // Suggest better alternatives
    const alternatives = ["#000000", "#ffffff"];
    const better = tinycolor.mostReadable(bgColor, alternatives);
    suggestions.push(`Try ${better.toHexString()} for better contrast`);
  }
  
  return {
    ratio: ratio,
    compliance: compliance,
    suggestions: suggestions,
    grade: compliance["AAA-small"] ? "AAA" : 
           compliance["AA-small"] ? "AA" : 
           compliance["AA-large"] ? "AA (large text only)" : "Fail"
  };
}

// Usage
const analysis = analyzeColorAccessibility("#3498db", "#ffffff");
console.log(analysis);

Design System Validation

function validateDesignSystem(colorPalette) {
  const results = {};
  
  // Test all background/text combinations
  Object.keys(colorPalette.backgrounds || {}).forEach(bgName => {
    const bgColor = colorPalette.backgrounds[bgName];
    results[bgName] = {};
    
    Object.keys(colorPalette.text || {}).forEach(textName => {
      const textColor = colorPalette.text[textName];
      const ratio = tinycolor.readability(bgColor, textColor);
      const isAA = tinycolor.isReadable(bgColor, textColor);
      const isAAA = tinycolor.isReadable(bgColor, textColor, {level: "AAA"});
      
      results[bgName][textName] = {
        ratio: ratio,
        AA: isAA,
        AAA: isAAA,
        status: isAAA ? "AAA" : isAA ? "AA" : "FAIL"
      };
    });
  });
  
  return results;
}

// Example color palette
const myPalette = {
  backgrounds: {
    primary: "#2c3e50",
    secondary: "#3498db", 
    light: "#ecf0f1",
    white: "#ffffff"
  },
  text: {
    primary: "#2c3e50",
    secondary: "#7f8c8d",
    light: "#bdc3c7",
    white: "#ffffff"
  }
};

const validation = validateDesignSystem(myPalette);

Automatic Color Adjustment

function makeReadable(backgroundColor, textColor, targetLevel = "AA", targetSize = "small") {
  const bgColor = tinycolor(backgroundColor);
  let txtColor = tinycolor(textColor);
  
  // If already readable, return as-is
  if (tinycolor.isReadable(bgColor, txtColor, {level: targetLevel, size: targetSize})) {
    return txtColor.toHexString();
  }
  
  // Try to adjust the text color to meet requirements
  const bgLuminance = bgColor.getLuminance();
  const shouldBeDark = bgLuminance > 0.5;
  
  // Start with pure black or white based on background
  const startColor = shouldBeDark ? "#000000" : "#ffffff";
  let adjustedColor = tinycolor(startColor);
  
  // If pure black/white doesn't work, gradually adjust towards original color
  if (!tinycolor.isReadable(bgColor, adjustedColor, {level: targetLevel, size: targetSize})) {
    // This is rare, but handle edge cases
    const alternatives = ["#000000", "#ffffff", "#333333", "#cccccc"];
    adjustedColor = tinycolor.mostReadable(bgColor, alternatives, {
      includeFallbackColors: true,
      level: targetLevel,
      size: targetSize
    });
  }
  
  return adjustedColor.toHexString();
}

// Usage
const readableText = makeReadable("#ff6b6b", "#ffcc02", "AA", "small");
console.log("Adjusted text color:", readableText);

Color Blindness Considerations

function checkColorBlindAccessibility(color1, color2) {
  const c1 = tinycolor(color1);
  const c2 = tinycolor(color2);
  
  // Convert to different color spaces for analysis
  const rgb1 = c1.toRgb();
  const rgb2 = c2.toRgb();
  
  // Simulate common color blindness types (simplified)
  const protanopia = {
    // Red-blind simulation (simplified)
    color1: tinycolor({r: rgb1.g, g: rgb1.g, b: rgb1.b}),
    color2: tinycolor({r: rgb2.g, g: rgb2.g, b: rgb2.b})
  };
  
  const deuteranopia = {
    // Green-blind simulation (simplified)
    color1: tinycolor({r: rgb1.r, g: rgb1.r, b: rgb1.b}),
    color2: tinycolor({r: rgb2.r, g: rgb2.r, b: rgb2.b})
  };
  
  return {
    normal: tinycolor.readability(c1, c2),
    protanopia: tinycolor.readability(protanopia.color1, protanopia.color2),
    deuteranopia: tinycolor.readability(deuteranopia.color1, deuteranopia.color2),
    // Add tritanopia and other types as needed
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-tinycolor2

docs

accessibility.md

color-analysis.md

color-conversion.md

color-creation.md

color-modification.md

color-schemes.md

index.md

utilities.md

tile.json