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.