Sort colors by luminance
Turn an unordered group of hex colors into a predictable darkest-to-lightest scale.
Run — free
This tool validates every value, normalizes short and full hex notation, converts sRGB channels to linear light, and sorts by WCAG relative luminance. Each result includes the normalized color and its luminance, so you can inspect close calls instead of trusting a visual guess. Equal-luminance colors keep their original order, duplicates remain present, and identical inputs always produce identical output. It is useful for design tokens, charts, themes, palette documentation, and any workflow that needs a reproducible brightness sequence rather than a subjective arrangement.
Build a brightness scale from an unordered palette
Color collections often arrive in an order that reflects when tokens were created, how a designer selected swatches, or how a source file serialized an object. None of those orders reliably communicates brightness. Provide the colors array as hex values and this capability returns sorted records from the lowest relative luminance to the highest. Three-digit forms such as #abc are expanded, six-digit forms are accepted with or without the leading hash, and every returned value uses lowercase #rrggbb notation. That normalization makes the result convenient for snapshots, token files, CSS generation, and comparisons between separate runs. Duplicates are not removed because repeated values can have meaning in an existing scale or parallel data structure. Colors with exactly equal computed luminance keep their input order, so ties never jump around between engines or executions. The response also reports the number of colors, the direction of the ordering, and the named method. Use the sorted array directly when constructing a sequential palette, or retain the luminance numbers as review evidence when neighboring swatches look unexpectedly close. The operation changes ordering only; it does not invent intermediate shades, rename tokens, or claim that the resulting steps are perceptually uniform.
Understand how relative luminance determines the order
Relative luminance is not a simple average of the red, green, and blue byte values. Hex colors encode sRGB channels with a nonlinear transfer curve, so each channel is first scaled to zero through one and converted to linear light. Values at or below the sRGB breakpoint are divided by 12.92; higher values use the standard power function. The linear red, green, and blue components are then combined with coefficients 0.2126, 0.7152, and 0.0722. Green contributes most, blue contributes least, and the final value runs from zero for black to one for white. Sorting uses the full unrounded result. The six-decimal luminance in each returned record is for readable output and stable serialization, not the comparison key, so two values that display the same rounded number can still appear in their mathematically correct order. This is the relative-luminance model commonly used in WCAG contrast calculations, but sorting a palette does not by itself establish accessibility. Contrast depends on a pair of colors, context, and applicable thresholds. Use this result to organize colors, then perform a dedicated contrast check for foreground and background combinations that must satisfy accessibility requirements.
Validate inputs and integrate deterministic results
The colors field must be a non-empty array with no more than one thousand entries. Every entry must be a string containing exactly three or six hexadecimal digits after an optional leading hash. Whitespace around the complete value is ignored, while alpha forms, CSS color names, rgb() functions, malformed lengths, and non-string entries are rejected. The error identifies the failing array index, which makes a bad token easy to locate in a generated palette. Validation is fail-fast: the capability never silently drops an invalid value or guesses how it should be interpreted. That behavior matters in automated design-system builds because a partial scale can look plausible while hiding a missing brand color. The implementation performs only local arithmetic and an in-memory sort. It does not call a network service, read the clock, use random values, or retain input, so the same array produces the same JSON in browser tools, CI jobs, and API calls. The browser runner is free, while an API request costs $0.002. When integrating, store the normalized returned colors if canonical formatting is desirable, or map the ordered records back to your own token metadata by occurrence. Because duplicates are preserved and ties are stable, positional mapping remains deterministic even when multiple source tokens share the same hex value or luminance.
What you can do with it
Order design tokens
Convert an inherited or generated set of hex tokens into a stable dark-to-light sequence before assigning scale numbers.
Prepare sequential chart colors
Arrange candidate swatches by relative luminance so a data-visualization scale progresses in a consistent direction.
Audit palette progression
Inspect luminance values beside normalized colors to find reversals, duplicates, or unusually small steps in a theme palette.
FAQ
What does an API request cost?
Each API request costs $0.002. You can also run the same deterministic solver free in the browser.
Which color formats are accepted?
Use three- or six-digit hex colors with an optional leading hash, such as #abc, abc, #aabbcc, or AABBCC.
Are alpha colors or CSS color names supported?
No. Alpha hex, named colors, rgb(), hsl(), and other CSS syntaxes are rejected so every input has unambiguous opaque sRGB channels.
What happens when two colors have equal luminance?
They remain in their original relative order. This stable tie rule also preserves duplicate entries deterministically.
Does darkest-to-lightest order guarantee accessible contrast?
No. Relative luminance ordering describes individual colors; accessibility contrast requires evaluating specific foreground and background pairs against the relevant threshold.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/color/sort-by-luminance \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"colors":["#f8fafc","#0f172a","#22c55e","#64748b"]}'const res = await fetch("https://api.kit.forhosting.com/color/sort-by-luminance", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"colors": [
"#f8fafc",
"#0f172a",
"#22c55e",
"#64748b"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/sort-by-luminance",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"colors": [
"#f8fafc",
"#0f172a",
"#22c55e",
"#64748b"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/sort-by-luminance", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"colors":["#f8fafc","#0f172a","#22c55e","#64748b"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"colors":["#f8fafc","#0f172a","#22c55e","#64748b"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/sort-by-luminance", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"colors": [
"#f8fafc",
"#0f172a",
"#22c55e",
"#64748b"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.sort_by_luminance",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_items | 1000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |