better-color-tools
JS and Sass color tools for the Oklab/Oklch colorspace for better color operations.
The JS version of this libray is fast (~200k
ops/s), lightweight (5.7 kB
gzip), and dependency-free. The Sass version… is Sass (which has no runtime).
🏀 Playground
Usage
npm install better-color-tools
Comparisons
This library exists to provide performant and accurate access to the Oklab and Oklch colorspaces at minimum filesize and maximum performance. This is not a comprehensive colorspace tool like Culori or Color.js, rather, better-color-tools
seeks to give you the best “bang for the buck” by providing maximum utility with minimal footprint.
To learn more, see Project Goals
JavaScript
Works in the browser (ESM) and Node (14+).
Importing
import better from 'better-color-tools';
Input
better.from('#b3f6e6');
better.from('rebeccapurple');
better.from('rgb(136, 48, 62)');
better.from('hsl(210, 85%, 37%)');
better.from('hwb(210, 6%, 31%)');
better.from('oklab(48.56949% -0.03971 -0.14459)');
better.from('oklch(83.11253% 0.22612 147.35276)');
better.from(0xb3f6e6);
better.from([0.533, 0.188, 0.243, 1]);
better.from({ r: 0.533, g: 0.188, b: 0.243, alpha: 1 });
better.from({ h: 210, s: 0.85, l: 0.37, alpha: 1 });
better.from({ h: 210, w: 0.06, b: 0.31, alpha: 1 });
better.from({ l: 0.4856949, a: -0.03971, b: -0.14459, alpha: 1 });
better.from({ l: 0.8311253, c: 0.22612, h: 147.35276, alpha: 1 });
This library understands any CSS-valid color, including CSS Color Module 4* (but if some aspect isn’t implemented yet, please request it!).
* With the exception that lab()
and lch()
CSS Module 4 colors will be interpreted as oklab()
and oklch()
, respectively.
Playground
Output
This library converts to most web-compatible formats¹, with an emphasis on usefulness over completeness:
const c = better.from('rgba(136, 48, 62, 1)');
c.hex;
c.rgb;
c.p3;
c.linearRGB;
c.oklab;
c.oklch;
c.xyz;
c.hexVal;
const [r, g, b, alpha] = c.rgbVal;
const [r, g, b, alpha] = c.p3Val;
const [lr, lg, lb, alpha] = c.linearRGBVal;
const [l, a, b, alpha] = c.oklabVal;
const [l, c, h, alpha] = c.oklchVal;
const [x, y, z, alpha] = c.xyzVal;
As a minor implementation detail, all of those properties are getters, so no work is wasted converting to colorspaces you haven’t explicitly asked for.
¹The following formats aren’t supported as outputs:
- HSL isn’t supported because you shouldn’t use it
- HWB isn’t supported because it’s another form of HSL
- CIELAB/CIELCh aren’t supported because Oklab/Oklch are superior
For a comprehensive color conversion library, see Culori.
Adjust
To adjust a color via Oklch, append .adjust()
along with the adjustments to make:
better.from('#0060ff').adjust({ lightness: 0.5 });
better.from('#0060ff').adjust({ mode: 'relative', lightness: -0.1 });
You can adjust lightness
, chroma
, and hue
altogether, and you can either operate in relative
or absolute
(default) mode.
P3
This library supports P3 by expanding the sRGB space into the P3 gamut 1:1. For example, 100% red sRGB is converted to 100% red P3:
const red = '#ff0000';
better.from(red).rgb;
better.from(red).p3;
Playground
This is the practice recommended by WebKit because when dealing with web colors you probably intend to take full advantage of that expanded gamut and this is the easiest, quickest way to do so without dealing with the specifics of both the sRGB and
P3 gamuts. This gives you more vibrant colors in supporting browsers without your colors appearing “off.”
While you wouldn’t want to use this technique for other methods such as photo manipulation, for CSS purposes this method is ideal (which better-color-tools assumes you’re using this for). Any deviation between this library’s implementation of P3 from
others’ are intentional.
Mix
This uses Oklab (best) by default, but also supports oklch
, lms
, sRGB
, and linearRGB
colorspaces for mixing.
better.mix('red', 'lime', 0.35);
better.mix('blue', 'magenta', 0.5, 'linearRGB');
Playground
Lighten / Darken
This takes hue and human perception into account for visually-accurate results. Also, fun fact: both functions accept negative values.
better.lighten('red', 0.5);
better.darken('red', 0.5);
Playground
Lightness
Get the human-perceived lightness of any color (identical to the first value of .oklabVal
)
better.lightness('#fc7030');
Playground
Adjust
Manipulation is done through Oklch which is optimized for manual tweaking. Call the .adjust()
method to manipulate the color (they can even be chained together for multiple, incremental adjustments
import better from 'better-color-tools';
better
.from('#5a00a6')
.adjust({
mode: 'relative',
hue: 5,
chroma: 0.1,
})
.adjust({
mode: 'absolute',
lightnes: 0.6,
}).hex;
Note that if mode
is omitted, the default value is 'absolute'
.
Contrast Ratio
Get WCAG 2.1 contrast ratio for 2 colors. The order doesn’t matter.
better.contrastRatio('#37ca93', '#055af6');
better.contrastRatio('#37ca93', '#4474cc');
better.contrastRatio('#37ca93', '#002c7b');
Light or dark?
Should you overlay white or black text over a color? This will figure out whether a color is perceptually “dark” or “light,” taken directly from Myndex’s “flip for color” technique. You can
then use white text for dark colors, and vice-versa.
Note: though it would be reasonable to assume this just checks whether Oklab’s l
(lightness) value is over or under 0.5
, there’s a bit more to it)
better.lightOrDark('#2d659e');
better.lightOrDark('#b2d6d3');
Sass
Works with any version of Dart Sass (the current version).
Importing
@use 'better-color-tools' as better;
Mix
.foo {
color: better.mix('red', 'lime', 0.35);
}
Uses Oklab for best results (which yields much better results than Sass’ mix).
Lighten / Darken
.foo:hover {
color: better.lighten('blue', 0.2);
border-color: better.darken('blue', 0.15);
}
Lightens (or darkens) color via Oklab for human-perceived lightness value (which yields much better results than Sass’ lighten/darken:
P3
.foo {
color: better.p3(#f00);
}
Convert any Sass-readable color to P3.
Fallback
.foo {
@include better.fallback('color', better.p3(#f00), #f00);
}
Mixin for adding CSS fallbacks. Can take infinite arguments. Specify desired format first, followed by fallbacks.
Oklab/Oklch
$oklab: better.rgbToOklab(#f00);
$oklch: better.rgbToOklch(#f00);
$rgb: better.oklabToRGB($oklab);
$rgb: better.oklchToRGB($oklch);
Converts any Sass-readable color to an Oklab map of (l: …, a: …, b: …)
, or Oklch map of (l: …, c: …, h: …)
. The Sass map can either be used to make a CSS string:
@use 'sass:map';
.foo {
color: oklab(#{map.get($oklab, 'l')} #{map.get($oklab, 'a')} #{map.get($oklab, 'b')});
color: better.oklabToRGB($oklab);
}
Or for color manipulation:
$oklab-lighter: map.set($oklab, 'l', 0.8);
Lightness
Get the human-perceived lightness of any color (identical to the first value of .oklabVal
):
$lightness: better.lightness(#f00);