diff --git a/docs/_data/palettes.js b/docs/_data/palettes.js
new file mode 100644
index 000000000..fb231f0e9
--- /dev/null
+++ b/docs/_data/palettes.js
@@ -0,0 +1 @@
+export { default as default } from '../../src/styles/color/palettes.js';
diff --git a/docs/_includes/contrast-table.njk b/docs/_includes/contrast-table.njk
index 19b4bec7f..46e6ff701 100644
--- a/docs/_includes/contrast-table.njk
+++ b/docs/_includes/contrast-table.njk
@@ -15,15 +15,24 @@
| {{ hue | capitalize }} |
{% for tint_bg in tints -%}
- {% for tint_fg in tints | reverse -%}
-
- {% if (tint_fg - tint_bg) | abs == difference %}
-
-
- {{ tint_fg }} on {{ tint_bg }}
-
- |
- {% endif %}
+ {% set color_bg = palettes[paletteId][hue][tint_bg] %}
+ {% for tint_fg in tints | reverse -%}
+ {% set color_fg = palettes[paletteId][hue][tint_fg] %}
+ {% if (tint_fg - tint_bg) | abs == difference %}
+
+
+ {% set contrast_wcag = '' %}
+ {% if color_fg and color_bg %}
+ {% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
+ {% endif %}
+ {% if contrast_wcag %}
+ {{ contrast_wcag | number({maximumSignificantDigits: 2}) }}
+ {% else %}
+ {{ tint_fg }} on {{ tint_bg }}
+ {% endif %}
+
+ |
+ {% endif %}
{%- endfor -%}
{%- endfor -%}
diff --git a/docs/_layouts/palette.njk b/docs/_layouts/palette.njk
index 03fa26cd5..a0e19a13b 100644
--- a/docs/_layouts/palette.njk
+++ b/docs/_layouts/palette.njk
@@ -61,6 +61,16 @@ A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for larg
{% include "contrast-table.njk" %}
{% markdown %}
+
+This also goes for a difference of `45`:
+
+{% endmarkdown %}
+
+{% set difference = 45 %}
+{% include "contrast-table.njk" %}
+
+{% markdown %}
+
### Level 2
A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for normal text (AA) and large text (AAA)
@@ -69,6 +79,15 @@ A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for no
{% set difference = 50 %}
{% include "contrast-table.njk" %}
+{% markdown %}
+
+This also goes for a difference of `55`:
+
+{% endmarkdown %}
+
+{% set difference = 55 %}
+{% include "contrast-table.njk" %}
+
{% markdown %}
### Level 3
@@ -78,6 +97,15 @@ A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all
{% set difference = 60 %}
{% include "contrast-table.njk" %}
+{% markdown %}
+
+This also goes for a difference of `65`:
+
+{% endmarkdown %}
+
+{% set difference = 65 %}
+{% include "contrast-table.njk" %}
+
{% markdown %}
## How to use this palette
diff --git a/docs/_utils/filters.js b/docs/_utils/filters.js
index 5f672881a..912b5dbb1 100644
--- a/docs/_utils/filters.js
+++ b/docs/_utils/filters.js
@@ -105,6 +105,23 @@ export function deepValue(obj, key) {
return key.reduce((subObj, property) => subObj?.[property], obj);
}
+export function number(value, options) {
+ if (typeof value !== 'number' && isNaN(value)) {
+ return value;
+ }
+
+ let lang = options?.lang ?? 'en';
+ if (options?.lang) {
+ delete options.lang;
+ }
+
+ if (!options || Object.keys(options).length === 0) {
+ options = { maximumSignificantDigits: 3 };
+ }
+
+ return Number(value).toLocaleString(lang, options);
+}
+
export function isNumeric(value) {
return typeof value === 'number' || (typeof value === 'string' && !isNaN(value));
}
diff --git a/package.json b/package.json
index 8d0591ef9..e1c0d10bd 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"create": "plop --plopfile scripts/plop/plopfile.js",
"test": "web-test-runner --group default",
"test:component": "web-test-runner -- --watch --group",
+ "test:contrast": "cd src/styles/color && node contrast.test.js",
"test:watch": "web-test-runner --watch --group default",
"prettier": "prettier --check --log-level=warn .",
"prettier:fix": "prettier --write --log-level=warn .",
diff --git a/src/styles/color/contrast.test.js b/src/styles/color/contrast.test.js
index 2be37b56a..1882f8353 100644
--- a/src/styles/color/contrast.test.js
+++ b/src/styles/color/contrast.test.js
@@ -1,34 +1,7 @@
// Get a list of all CSS files in repo
import chalk from 'chalk';
import Color from 'colorjs.io';
-import fs from 'fs';
-import path from 'path';
-import { fileURLToPath } from 'url';
-
-const __dirname = fileURLToPath(new URL('.', import.meta.url));
-
-const paletteFiles = fs.readdirSync(__dirname).filter(file => file.endsWith('.css'));
-
-function parse(contents) {
- // Regex for each declaration
- let regex = /^\s*--wa-color-(?[a-z]+)-(?[0-9]+):\s*(?[^;]+)\s*;$/gm;
- let matches = [...contents.matchAll(regex)];
-
- if (matches.length === 0) {
- throw new Error('Cound not extract colors');
- }
-
- let ret = {};
-
- for (let match of matches) {
- let { hue, level, color } = match.groups;
- ret[hue] ??= {};
- level = level.replace(/^0+/, ''); // Leading zeroes throw off sorting
- ret[hue][level] = color;
- }
-
- return ret;
-}
+import palettes from './palettes.js';
let targetContrasts = {
40: 3,
@@ -38,21 +11,17 @@ let targetContrasts = {
let result = { pass: 0, fail: 0, invalid: 0 };
-for (let file of paletteFiles) {
- let css = fs.readFileSync(path.join(__dirname, file), 'utf8');
- let filePrefix = chalk.dim(`[${file}]`);
- let tokens = parse(css);
+for (let paletteId in palettes) {
+ const tokens = palettes[paletteId];
+ let prefix = chalk.dim(`[${paletteId}]`);
for (let hue in tokens) {
let tints = tokens[hue];
for (let tint = 10; tint <= 50; tint += 10) {
- let color;
+ let color = tints[tint];
- try {
- color = new Color(tints[tint]);
- } catch (e) {
- console.error(`[${file}] Invalid color ${hue}-${tint}: ${tints[tint]}`);
+ if (!(color instanceof Color)) {
result.invalid++;
continue;
}
@@ -64,15 +33,10 @@ for (let file of paletteFiles) {
continue;
}
- let color2;
- try {
- color2 = new Color(tints[tint2]);
- } catch (e) {
- if (tint2 > 50) {
- // If 50, we'll look at it at some point as color1
- console.error(`${filePrefix} Invalid color ${hue}-${tint2}: ${tints[tint2]}`);
- result.invalid++;
- }
+ let color2 = tints[tint2];
+
+ if (!(color2 instanceof Color)) {
+ result.invalid++;
continue;
}
@@ -84,7 +48,7 @@ for (let file of paletteFiles) {
result.fail++;
console.log(
chalk.red(
- `${filePrefix} WCAG 2.1 contrast between ${hue}-${tint} and ${hue}-${tint2} is ${contrast.toLocaleString('en')} < ${targetContrast}`,
+ `${prefix} WCAG 2.1 contrast between ${hue}-${tint} and ${hue}-${tint2} is ${contrast.toLocaleString('en')} < ${targetContrast}`,
),
);
}
diff --git a/src/styles/color/palettes.js b/src/styles/color/palettes.js
new file mode 100644
index 000000000..130c3e02a
--- /dev/null
+++ b/src/styles/color/palettes.js
@@ -0,0 +1,63 @@
+/**
+ * Export data on all color tokens from all palettes
+ */
+
+// Get a list of all CSS files in repo
+import Color from 'colorjs.io';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url));
+
+export const paletteFiles = fs.readdirSync(__dirname).filter(file => file.endsWith('.css'));
+export const declarationRegex = /^\s*--wa-color-(?[a-z]+)-(?[0-9]+):\s*(?[^;]+)\s*;$/gm;
+
+function parse(contents, file) {
+ // Regex for each declaration
+ const matches = [...contents.matchAll(declarationRegex)];
+
+ if (matches.length === 0) {
+ throw new Error('Cound not extract colors');
+ }
+
+ const ret = {};
+
+ for (let match of matches) {
+ let { hue, level, color } = match.groups;
+ ret[hue] ??= {};
+
+ // Attempt to convert color to Color object, fall back to string if this fails
+ // This will happen for e.g. colors defined via color-mix()
+ try {
+ color = new Color(color);
+ } catch (e) {
+ console.warn(`[${file}] Unparseable color ${hue}-${level}: ${color}`);
+ }
+
+ if (level.startsWith('0')) {
+ // Leading zeroes throw off sorting, add both properties
+ // NOTE: Ideally one of the two would be added as non-enumerable, but then we cannot access it via 11ty data
+ ret[hue][level] = color;
+
+ // Drop leading zeroes
+ level = level.replace(/^0+/, '');
+ }
+
+ ret[hue][level] = color;
+ }
+
+ return ret;
+}
+
+const palettes = {};
+
+for (let file of paletteFiles) {
+ let css = fs.readFileSync(path.join(__dirname, file), 'utf8');
+ let tokens = parse(css, file);
+ let paletteId = file.replace(/\.css$/, '');
+
+ palettes[paletteId] = tokens;
+}
+
+export default palettes;