Darken hex color by percent
Build hover and pressed shades from one brand hex with a fixed percentage, so #4A90D9 at 20 % turns into #2873C0, still the same blue. Lightness shrinks in proportion and stops at black, never below. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Color
- #4A90D9
- Percent
- 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, clamps the result at black, 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 percent over one hundred, then clamps the result so lightness never goes below zero.
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 with a black clamp always stays in range and never invents a negative lightness.
Input contract: hex colors and the 0–100 percent
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 percent 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 percent 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 percent zero to normalize and expand short hex without changing shade, or percent one hundred when you need a true black anchor derived from the same code path as partial darkens for consistent token pipelines.
For one swatch or a full set of states
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 percent.
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 with clamped black at full percent.
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 darken hex by percent accept?
A hex color (#RGB or #RRGGBB, optional #) and a percent from 0 to 100.
How is the darker color calculated?
The hex is converted to HSL, lightness is multiplied by (1 − percent/100) and clamped at zero (black), then converted back to hex, RGB, and HSL. Hue and saturation stay the same; percent 100 yields black.
What does a successful response contain?
original (normalized input hex), hex (darkened result), percent, 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 percent and clamps at black.
Why use a relative percent instead of subtracting lightness points?
Subtracting fixed points can overshoot on colors that are already dark. Scaling lightness by a percent always stays in range: 50 % halves it, 100 % reaches black, and nothing goes negative.
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-percent \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"color":"#4A90D9","percent":20}'const res = await fetch("https://api.kit.forhosting.com/color/darken-percent", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"color": "#4A90D9",
"percent": 20
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/darken-percent",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"color": "#4A90D9",
"percent": 20
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/darken-percent", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"color":"#4A90D9","percent":20}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"color":"#4A90D9","percent":20}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/darken-percent", 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",
"percent": 20
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.darken_percent",
"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 and malformed hex strings 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 percent
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 amount, pct, or percentage for the percent.
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 percents 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.