Hex Color Contrast: A Developer’s Guide to Leonardo JS API for Accessible Design

Ensuring optimal color contrast is paramount in modern web design to guarantee accessibility and readability for all users. The Leonardo JS API, developed by Adobe, offers a robust solution for generating adaptive, contrast-based color palettes. This guide delves into how developers can leverage the Leonardo API to effectively manage and compare hex color contrasts, ensuring their designs meet accessibility standards.

Getting Started with Leonardo JS API

Integrating the Leonardo API into your development environment is straightforward. Begin by installing the package using npm:

npm install @adobe/leonardo-contrast-colors

Once installed, import the necessary modules into your project. Depending on your Node.js environment, use either CommonJS (CJS) or ECMAScript Modules (ESM):

CJS (Node 12.x)

const { Theme, Color, BackgroundColor } = require('@adobe/leonardo-contrast-colors');

ESM (Node 13.x and above)

import { Theme, Color, BackgroundColor } from '@adobe/leonardo-contrast-colors';

With the package imported, you can start creating color themes. This involves defining base colors and a background color, then instantiating a Theme object.

let gray = new BackgroundColor({ name: 'gray', colorKeys: ['#cacaca'], ratios: [2, 3, 4.5, 8] });
let blue = new Color({ name: 'blue', colorKeys: ['#5CDBFF', '#0000FF'], ratios: [3, 4.5] });
let red = new Color({ name: 'red', colorKeys: ['#FF9A81', '#FF0000'], ratios: [3, 4.5] });
let theme = new Theme({colors: [gray, blue, red], backgroundColor: gray, lightness: 97});
let colors = theme.contrastColors; // Access theme colors as JSON

This snippet demonstrates how to create a basic theme with gray as the background and blue and red as foreground colors, each with specified contrast ratios. The theme.contrastColors output provides a JSON object containing the generated color palette.

API Deep Dive: Theme Class

The Theme class is central to the Leonardo API, designed to generate adaptive color schemes based on contrast. It accepts several parameters to customize theme generation:

Parameter Type Description
colors Array An array of Color classes, including at least one BackgroundColor class.
lightness Number Desired lightness (0-100) for the generated background color.
contrast Number Multiplier to adjust contrast for all theme colors (default: 1).
saturation Number Value (0-100) to decrease saturation of theme colors (default: 100).
output Enum Specifies the desired color output format (e.g., HEX, RGB, HSL).

Theme Setters for Dynamic Adjustments

The Theme class also provides setters to dynamically modify theme properties after instantiation:

Setter Description
Theme.lightness Adjusts the theme’s lightness.
Theme.contrast Modifies the theme’s contrast level.
Theme.saturation Alters the theme’s saturation.
Theme.backgroundColor Changes the theme’s background color.
Theme.colors Replaces the theme’s colors (must be Color instances).
Theme.output Updates the output format for theme colors.
Theme.addColor Adds a new Color to the theme.
Theme.removeColor Removes a Color from the theme.
Theme.updateColor Modifies properties of an existing Color within the theme.

Adding, Removing, and Updating Colors

The API allows for dynamic manipulation of colors within a theme.

Adding a Color:

let red = new Color({ name: 'red', colorKeys: ['#FF9A81', '#FF0000'], ratios: [3, 4.5] });
theme.addColor = red;

Removing a Color:

// Remove by color name
theme.removeColor = {name: 'Red'};
// Remove by Color class instance
const red = new Color({ name: 'red', colorKeys: ['#FF9A81', '#FF0000'], ratios: [3, 4.5] });
theme.removeColor = red;

Updating Color Properties:

// Change color ratios
theme.updateColor = {color: 'red', ratios: [3, 4.5, 7]};
// Change color keys
theme.updateColor = {color: 'red', colorKeys: ['#ff0000']};
// Change color name
theme.updateColor = {color: 'red', name: 'Crimson'};

Multiple properties can be updated simultaneously:

theme.updateColor = {color: 'red', ratios: [3, 4.5, 7], colorKeys: ['#ff0000'], name: 'Crimson'};

Supported Output Formats for Hex Color Comparison

Leonardo API supports various color formats, adhering to the W3C CSS Color Module Level 4 specification. This is crucial when you need to compare hex colors in different formats or convert them for specific applications.

Output Option Sample Value
'HEX' (default) #RRGGBB
'RGB' rgb(255, 255, 255)
'HSL' hsl(360deg, 0%, 100%)
'HSV' hsv(360deg, 0%, 100%)
'HSLuv' hsluv(360, 0, 100)
'LAB' lab(100%, 0, 0)
'LCH' lch(100%, 0, 360deg)
'CAM02' jab(100%, 0, 0)
'CAM02p' jch(100%, 0, 360deg)

API Deep Dive: Color Class

The Color class is used to define individual colors within a theme. Key parameters include:

Parameter Type Description
name String User-defined name for the color (e.g., “Blue”).
colorKeys Array of strings Array of hex color codes to define the color range.
colorspace Enum Colorspace for interpolation (e.g., ‘LAB’, ‘LCH’, ‘RGB’).
ratios Array or Object Target contrast ratios or named ratios for specific semantic meanings.
smooth Boolean Applies bezier smoothing to color interpolation (default: false).
output Enum Desired color output format.

Color Setters for Granular Control

Similar to the Theme class, Color objects have setters for modifying their properties:

Setter Description
Color.colorKeys Sets the color keys (hex codes).
Color.colorspace Defines the interpolation colorspace.
Color.ratios Modifies the contrast ratios.
Color.name Renames the color.
Color.smooth Toggles bezier smoothing.
Color.output Changes the output format.

Interpolation Colorspaces

Leonardo supports interpolation between colors in various colorspaces, influencing how color scales are generated. The choice of colorspace affects the visual uniformity and perceived lightness progression of the generated scale.

Defining Ratios for Hex Color Contrast

Ratios can be defined as a flat array or as an object, offering flexibility in naming and semantic clarity.

Ratios as an Array:

new Color({
  name: 'blue',
  colorKeys: ['#5CDBFF', '#0000FF'],
  colorSpace: 'LCH',
  ratios: [3, 4.5]
});

This configuration generates color variations named numerically (e.g., blue100, blue200) based on the contrast ratio.

Ratios as an Object:

new Color({
  name: 'blue',
  colorKeys: ['#5CDBFF', '#0000FF'],
  colorSpace: 'LCH',
  ratios: {
    'blue--largeText': 3,
    'blue--normalText': 4.5
  }
});

Using an object allows for semantic naming of color variations (e.g., blue--largeText, blue--normalText), improving code readability and maintainability.

Output Examples: Comparing Hex Color Palettes

The Theme class provides different getters to access the generated color palette in various formats, facilitating hex color comparison and integration into different design systems.

Getter Description
Theme.contrastColors Returns an array of color objects with detailed properties.
Theme.contrastColorPairs Returns a simplified object with key-value pairs (name: hex color).
Theme.contrastColorValues Returns a flat array of hex color values.

Theme.contrastColors Output

This getter provides a structured array, ideal for detailed inspection and manipulation of each color in the palette.

[
  { background: "#e0e0e0" },
  {
    name: 'gray',
    values: [
      { name: "gray100", contrast: 1, value: "#e0e0e0" },
      { name: "gray200", contrast: 2, value: "#a0a0a0" },
      { name: "gray300", contrast: 3, value: "#808080" },
      { name: "gray400", contrast: 4.5, value: "#646464" }
    ]
  },
  {
    name: 'blue',
    values: [
      { name: "blue100", contrast: 2, value: "#b18cff" },
      { name: "blue200", contrast: 3, value: "#8d63ff" },
      { name: "blue300", contrast: 4.5, value: "#623aff" },
      { name: "blue400", contrast: 8, value: "#1c0ad1" }
    ]
  }
]

Theme.contrastColorPairs Output

This output format is a straightforward object, perfect for direct application in CSS or JavaScript where key-value pairs are easily consumed.

{
  "gray100": "#e0e0e0";
  "gray200": "#a0a0a0";
  "gray300": "#808080";
  "gray400": "#646464";
  "blue100": "#b18cff";
  "blue200": "#8d63ff";
  "blue300": "#623aff";
  "blue400": "#1c0ad1";
}

Theme.contrastColorValues Output

For scenarios requiring a simple array of hex color codes, contrastColorValues provides a flat array.

[
  "#e0e0e0", "#a0a0a0", "#808080", "#646464",
  "#b18cff", "#8d63ff", "#623aff", "#1c0ad1"
]

Integrating Leonardo with CSS Variables

Leonardo API seamlessly integrates with CSS variables, enabling dynamic theme adjustments in web applications.

Vanilla JavaScript Implementation

let varPrefix = '--';
for (let i = 0; i < myTheme.length; i++) {
  for (let j = 0; j < myTheme[i].values.length; j++) {
    let key = myTheme[i].values[j].name;
    let prop = varPrefix.concat(key);
    let value = myTheme[i].values[j].value;
    document.documentElement.style.setProperty(prop, value);
  }
}

This code snippet iterates through the generated theme and sets CSS variables for each color, allowing for easy theme application in CSS.

React Implementation

In React applications, you can create a Theme component to manage and apply themes using CSS variables.

Theme.js Component:

import * as Leo from '@adobe/leonardo-contrast-colors';

const Theme = () => {
  let gray = new Leo.BackgroundColor({ name: 'gray', colorKeys: ['#cacaca'], ratios: [2, 3, 4.5, 8] });
  let blue = new Leo.Color({ name: 'blue', colorKeys: ['#5CDBFF', '#0000FF'], ratios: [3, 4.5] });
  let red = new Leo.Color({ name: 'red', colorKeys: ['#FF9A81', '#FF0000'], ratios: [3, 4.5] });
  const adaptiveTheme = new Leo.Theme({ colors: [ gray, blue, red ], backgroundColor: gray, lightness: 97, contrast: 1, });
  return adaptiveTheme;
}
export default Theme;

App.js Integration:

import {useTheme} from 'css-vars-hook';
import Theme from './components/Theme';
import React, { useState } from 'react';

function App(props) {
  const [lightness, setLightness] = useState(100);
  const [contrast, setContrast] = useState(1);

  const _createThemeObject = () => {
    let themeObj = {};
    props.adaptiveTheme.contrastColors.forEach(color => {
      if(color.name) {
        let values = color.values;
        values.forEach(instance => {
          let name = instance.name;
          let val = instance.value;
          themeObj[name] = val;
        });
      } else {
        let name = 'background'
        let val = color.background;
        themeObj[name] = val;
      }
    })
    return themeObj;
  };

  const theme = useState( _createThemeObject() );
  const {setRef, setVariable} = useTheme(theme);

  function _updateColorVariables() {
    let themeInstance = _createThemeObject();
    for (const [key, value] of Object.entries( themeInstance )) {
      setVariable(key, value);
    }
  };
  _updateColorVariables();

  return (
    <div className="App" ref={setRef} >
      {/* ... rest of your app content */}
      <label htmlFor="lightness"> Lightness
        <input value={lightness} id="lightness" type="range" min={ 0 } max={ 100 } step="1" onChange={e => {
          setLightness(e.target.value)
          props.adaptiveTheme.lightness = e.target.value
          _updateColorVariables()
        }} />
      </label>
      <label htmlFor="contrast"> Contrast
        <input value={contrast} id="contrast" type="range" min="0.25" max="3" step="0.025" onChange={e => {
          setContrast(e.target.value)
          props.adaptiveTheme.contrast = e.target.value
          _updateColorVariables()
        }} />
      </label>
    </div>
  )
}

const adaptiveTheme = Theme();

function AppWrapper() {
  return (
    <React.StrictMode>
      <App adaptiveTheme={adaptiveTheme} />
    </React.StrictMode>
  );
}


export default AppWrapper;

This React example uses the css-vars-hook to manage CSS variables and dynamically update the theme based on user interactions, such as slider adjustments for lightness and contrast.

Dark Mode Support in React

Implementing dark mode can be achieved by listening to the user’s system preferences and adjusting the theme accordingly.

const mq = window.matchMedia('(prefers-color-scheme: dark)');
const [lightness, setLightness] = useState((mq.matches) ? 8 : 100);
const [sliderMin, setSliderMin] = useState((mq.matches) ? 0 : 80);
const [sliderMax, setSliderMax] = useState((mq.matches) ? 30 : 100);

mq.addEventListener('change', function (evt) {
  props.adaptiveTheme.lightness = ((mq.matches) ? 11 : 100)
  setLightness((mq.matches) ? 11 : 100)
  setSliderMin((mq.matches) ? 0 : 80);
  setSliderMax((mq.matches) ? 30 : 100);
});

This code snippet detects the user’s preferred color scheme and adjusts the initial lightness and slider range to suit dark or light mode, enhancing accessibility and user experience.

Understanding Contrast Ratio Limitations

The Leonardo API aims to meet specified contrast ratios, but due to the discrete nature of RGB color space, exact ratios may not always be achievable. The API prioritizes generating colors that meet at least the target contrast, ensuring accessibility compliance.

Powered by Chroma.js

Leonardo API is built upon Chroma.js, enhanced with custom extensions for CIE CAM02 color space. This foundation ensures robust color manipulation and accurate color scale generation.

Contributing and Development

Contributions to the Leonardo project are welcome. Refer to the Contributing Guide for details on how to contribute.

For local development and testing:

npm run dev

Licensing Information

This project is licensed under the Apache V2 License. For more details, see the LICENSE file.

This guide provides a comprehensive overview of the Leonardo JS API, focusing on hex color contrast management and accessibility. By leveraging this API, developers can create dynamic, accessible, and visually appealing color palettes for their web applications.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *