Darken a hex color by percentage
Get a deeper shade of a hex color without guessing: #4A90D9 darkened by 20 % becomes #2873C0, with the same hue and saturation and less lightness. 0 % keeps the color, 100 % gives black. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Color
- #4A90D9
- Amount
- 20
- Original
- #4A90D9
- Hex
- #2873C0
Designers, front-end engineers, and design-system authors constantly need darker shades of a brand color for hover states, borders, shadows, and accessible text on light backgrounds. Guessing a new hex by eye is slow and inconsistent across tools. This capability accepts a hex color and a darken percentage from zero to one hundred, converts the color to HSL, multiplies lightness by one minus that percentage, and returns the darker hex with RGB and HSL breakdowns. Zero percent keeps the original color; one hundred percent yields black.
What darkening by percentage means in HSL
Colors on the web are often stored as hex or RGB, but perceptual shade steps are easier in HSL: hue holds the tint, saturation the purity, and lightness how close the sample sits to black or white. Darkening by a percentage means reducing lightness while keeping hue and saturation fixed, so a brand blue stays blue—just deeper. This tool multiplies current lightness by one minus amount over one hundred.
At zero percent the lightness is unchanged, so the output matches the normalized input hex. At fifty percent lightness is halved. At one hundred percent lightness becomes zero and the result is pure black regardless of the starting hue.
That relative model matches how many design tokens scale interactive states: a single base token plus a darken factor for hover or pressed. Absolute subtraction of lightness points can overshoot on already-dark colors; relative scaling always stays in range and never invents a negative lightness.
Input contract: hex colors and the 0–100 amount
Pass color as a hex string in short or long form: #RGB or #RRGGBB, with or without the leading hash. Short forms expand channel-wise so #F0A becomes #FF00AA before conversion. The amount field is required and must be a finite number between zero and one hundred inclusive.
Values such as negative ten, one hundred one, or non-numeric text are rejected; failed validation is not billed. On success the response includes original as the normalized uppercase six-digit hex, hex as the darkened result, the numeric amount you sent, rgb with integer channels zero through two hundred fifty-five, and hsl with hue in degrees and saturation and lightness on a zero-to-one-hundred scale after the darken step. Rounding is deterministic so re-running the same pair does not drift.
Use amount zero to normalize and expand short hex without changing shade, or amount one hundred when you need a true black anchor derived from the same code path as partial darkens.
Quick checks or whole palettes
For a one-off check—confirming that a primary blue darkens cleanly for a button hover, or that a twenty percent step still meets contrast goals against white—use the free in-browser tool on this page. There is no network call inside the transform, no model, and no clock dependency, so results are edge-safe and cacheable. When you already convert formats with a separate color converter, keep that for HEX–RGB–HSL round-trips; use this darkener when the search intent is specifically a darker shade by percentage.
Pair both in a pipeline if you need CMYK export after darkening, but the darken step itself only needs hex in and structured color out.
What you can do with it
UI hover and pressed states
Derive darker button and link shades from a single brand hex so hover and active styles stay consistent across components without hand-picking each swatch.
Email and PDF brand accents
Precompute darker accent hexes for borders and headers in templates that only accept static colors and cannot compute HSL at render time.
FAQ
What inputs does the color darkener accept?
A hex color (#RGB or #RRGGBB, optional #) and an amount percentage from 0 to 100.
How is the darker color calculated?
The hex is converted to HSL, lightness is multiplied by (1 − amount/100), then converted back to hex, RGB, and HSL. Hue and saturation stay the same; amount 100 yields black.
What does a successful response contain?
original (normalized input hex), hex (darkened result), amount, rgb {r,g,b}, and hsl {h,s,l} after darkening.
Is this the same as a color format converter?
No. A converter changes representation (HEX, RGB, HSL) without shifting shade. This tool specifically darkens by lowering lightness by a percentage.
What does a 20 % darken do to #4A90D9?
Its lightness is reduced by a fifth while hue and saturation stay put, so the result is #2873C0, or RGB 40, 115, 192. The blue stays the same blue, only deeper.
Related tools
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/darken \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"color":"#4A90D9","amount":20}'const res = await fetch("https://api.kit.forhosting.com/color/darken", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"color": "#4A90D9",
"amount": 20
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/darken",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"color": "#4A90D9",
"amount": 20
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/darken", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"color":"#4A90D9","amount":20}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"color":"#4A90D9","amount":20}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/darken", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"color": "#4A90D9",
"amount": 20
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.darken",
"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.
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. |
Technical notes
Amounts outside zero through one hundred are rejected with a clear invalid_input error. Use the free browser tool for quick swatches, or the API at $0.002 per successful request when you generate palettes in a pipeline.
Input contract: hex colors and the 0–100 amount
Invalid characters, wrong lengths, empty strings, and non-string values fail with invalid_input and a message that names the field. Aliases are accepted for convenience: hex, from, or text for the color, and percent, pct, or percentage for the amount.
Where to run it: free browser tool or automated API
The same pure function powers the API when you build theme generators, export design tokens to CSS custom properties, or precompute shade ladders for email templates that cannot run client scripts. Each successful API call costs $0.002; invalid amounts outside zero through one hundred and malformed hex do not consume prepaid balance. Drop the endpoint into a Figma plugin bridge, a Storybook addon that previews state colors, or a server step that materializes dark mode surfaces from a light base palette.
Integration use cases
Design token shade ladders
Generate stepped dark variants for CSS variables or theme JSON by applying fixed darken percentages to base palette colors in CI.
Technical questions
How much does the API cost?
$0.002 per successful request. The browser tool is free. Invalid input is not charged.