Skip to content

Instantly share code, notes, and snippets.

@mpalpha
Last active March 31, 2021 21:23
Show Gist options
  • Save mpalpha/d45cdd714d2ee152a36d84fb82f00ba2 to your computer and use it in GitHub Desktop.
Save mpalpha/d45cdd714d2ee152a36d84fb82f00ba2 to your computer and use it in GitHub Desktop.
Dynamically generate the first readable complementary color and the most readable color utilities from the current theme color palette.
// example tailwind.config.js
// ...
// theme: {
// readableText: {
// level: 'AAA',
// size: 'small'
// },
// }
// ...
//
// example classes (both required)
// bg-red-500 text-readable
// text-red-500 bg-readable
const plugin = require('tailwindcss/plugin')
const tinycolor = require('tinycolor2')
const flattenColorPalette = function (
root,
sep = '-',
ignore = ['transparent', 'current']
) {
return Object.assign(
{},
...(function _flatten(_object, _parent = '') {
return [].concat(
...Object.keys(_object)
.filter((_key) => !~ignore.indexOf(_key.toLowerCase()))
.map((_key) =>
typeof _object[_key] === 'object'
? _flatten(_object[_key], _key + sep)
: {
[(_parent + _key).replace(`${sep}default`, '')]:
_object[_key] + ''.replace(' ', '').toLowerCase(),
}
)
)
})(root)
)
}
const sortByReadability = (color, colors) =>
Object.values(colors).sort(function (a, b) {
return tinycolor.readability(color, a) - tinycolor.readability(color, b)
})
module.exports = plugin.withOptions(
({
readableTextLevel = 'AA', // 'AA' or 'AAA'
readableTextSize = 'small', // 'large' or 'small'
} = {}) => {
return function ({ e, theme, addUtilities, variants }) {
const colors = flattenColorPalette(theme('colors'))
const level = theme('readableText.level') || readableTextLevel
const size = theme('readableText.size') || readableTextSize
const readable = (color) => [
sortByReadability(color, colors)
.filter((c) =>
tinycolor.isReadable(color, tinycolor(c).toHexString(), {
level: level,
size: size,
})
)
.filter(
(c) =>
tinycolor(c).toHexString() ==
tinycolor(color).complement().toHexString()
)[0] ||
sortByReadability(color, colors).filter((c) =>
tinycolor.isReadable(color, tinycolor(c).toHexString(), {
level: level,
size: size,
})
)[0],
tinycolor
.mostReadable(color, sortByReadability(color, colors), {
includeFallbackColors: true,
level: level,
size: size,
})
.toHexString(),
]
addUtilities(
Object.keys(colors).reduce((acc, key) => {
if (typeof colors[key] === 'string') {
return {
...acc,
[`.text-readable.bg-${e(key)}`]: {
color: readable(colors[key])[0],
},
[`.text-most-readable.bg-${e(key)}`]: {
color: readable(colors[key])[1],
},
[`.bg-readable.text-${e(key)}`]: {
backgroundColor: readable(colors[key])[0],
},
[`.bg-most-readable.text-${e(key)}`]: {
backgroundColor: readable(colors[key])[1],
},
}
}
const colorVariants = Object.keys(colors[key])
return {
...acc,
...colorVariants.reduce(
(a, colorVariant) => (
{
...a,
[`.text-readable.bg-${e(key)}-${colorVariant}`]: {
color: readable(colors[key][colorVariant])[0],
},
[`.text-most-readable.bg-${e(key)}-${colorVariant}`]: {
color: readable(colors[key][colorVariant])[1],
},
[`.bg-readable.text-${e(key)}-${colorVariant}`]: {
backgroundColor: readable(colors[key][colorVariant])[0],
},
[`.bg-most-readable.text-${e(key)}-${colorVariant}`]: {
backgroundColor: readable(colors[key][colorVariant])[1],
},
},
{}
),
{}
),
}
}, {}),
variants('readableText')
)
}
},
() => ({ variants: { readableText: ['responsive', 'hover'] } })
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment