Nearest & random
Two jobs that keep coming back: a user hands you an arbitrary color and your design system only supports a fixed set, or you need a color for something that has none yet — an avatar, a tag, a chart series.
Nearest palette color
Section titled “Nearest palette color”Every palette has a getNearest() that takes any supported notation:
TailwindShade tw = TailwindPalette.getNearest("#3b82f6"); // blue-500OpenColorShade oc = OpenColorPalette.getNearest("#3b82f6"); // indigo-5ColorPalette rb = ColorPalette.getNearest("#3b82f6"); // COBALTAll three route through ColorCode.getNearest(), which measures distance in OKLAB:
OklabColor reference = given.toOklab();return candidates.stream() .min(Comparator.comparingDouble(c -> reference.distance(c.getRgbColor().toOklab()))) .orElse(null);OKLAB is what makes the answer trustworthy. A euclidean distance over rgb channels treats a step in blue and a step in green as equally large, which they are not — the eye is roughly ten times more sensitive to green. Matching in rgb therefore prefers colors that are numerically close and visibly wrong. In OKLAB the same numeric distance means the same perceived difference in every direction, so the closest number really is the closest-looking color.
You can measure directly, too:
double d = RgbColor.hex2rgb("#3b82f6").distance(RgbColor.hex2rgb("#2b7fff"));Values below roughly 0.02 are hard to tell apart side by side; above 0.1 the two colors read as different colors.
Restricting the search
Section titled “Restricting the search”Sometimes only part of the palette is acceptable — say the darker shades, because the result has to carry white text:
TailwindShade dark = TailwindPalette.getNearest(Shade.darks(), "#3b82f6");And with a set of your own colors:
List<TailwindShade> allowed = Arrays.asList( TailwindPalette.BLUE.shade(500), TailwindPalette.EMERALD.shade(500), TailwindPalette.AMBER.shade(500), TailwindPalette.ROSE.shade(500));
TailwindShade picked = ColorCode.getNearest(allowed, userPickedColor);ColorCode.getNearest() accepts any Collection<T extends ColorCode>, so the candidates can come
from different palettes at once, or from an enum of your own.
Shades
Section titled “Shades”Shade is the step within a family, from S50 up to S950. It carries both scales at once:
getTailwindStep() returns 50 … 950, getOpenColorStep() returns 0 … 9 (null for S950, which
Open Color has no counterpart for). One shade range therefore means the same thing in both palettes.
Shade.lights(); // S50 … S400Shade.mids(); // S400 … S600 — the accent rangeShade.darks(); // S600 … S950
Shade.range(Shade.S200, Shade.S700); // inclusive, always light to darkShade.ofTailwindStep(600); // S600Shade.ofOpenColorStep(6); // S600Shade.S700.isDark(); // trueRandom colors
Section titled “Random colors”TailwindShade any = TailwindPalette.randomShade(); // anything, 50 to 950TailwindShade accent = TailwindPalette.randomShade(Shade.mids()); // usable as an accentTailwindShade blue = TailwindPalette.BLUE.random(Shade.darks()); // dark blue
TailwindPalette family = TailwindPalette.randomFamily();Shade shade = Shade.random(Shade.lights());Restricting the shade range is what makes random colors usable. A random pick across the full scale
gives you slate-50 as often as red-600, and the near-white end is useless for an avatar
background while the 950 end is useless for a chart line. Shade.mids() keeps everything at a
comparable strength, so a set of random colors looks like a set instead of an accident.
Avatar colors, for instance, want to be stable per user rather than random on every request — hash the identifier and index into the palette:
List<TailwindShade> pool = new ArrayList<>();for (TailwindPalette family : TailwindPalette.values()) { pool.add(family.shade(Shade.S600));}
TailwindShade color = pool.get(Math.floorMod(userId.hashCode(), pool.size()));String background = color.getHexCodeWithLeadingHash();String text = color.isBlackContrastingColor2() ? "#000000" : "#ffffff";ColorPalette has its own two variants, since it has no shade structure:
ColorPalette.getRandomValue();ColorPalette.getRandomValue(alreadyUsed); // avoids everything in the collectionThe second one returns null once the collection covers the whole palette, so a caller assigning
colors to a growing list of items has to handle running out at 64.
Every color in one list
Section titled “Every color in one list”List<TailwindShade> all = TailwindPalette.all(); // 286List<OpenColorShade> allOc = OpenColorPalette.all(); // 130List<TailwindShade> reds = TailwindPalette.RED.allShades(); // 11, light to darkTailwindShade base = TailwindPalette.RED.base(); // red-500base() is the 500 shade, the one Tailwind means when it says “red”.