RGB to HEX: The 2026 Guide for Designers and Developers
RGB to HEX is base-10 to base-16 on three bytes. Here's the exact math, the JavaScript one-liners, alpha channels, and the mistakes that trip people up.
RGB to HEX: The 2026 Guide for Designers and Developers
You copy rgb(79, 70, 229) out of a design tool, but the codebase wants #4F46E5. They're the same indigo — one written in base-10, the other in base-16. Converting between them is a fixed piece of arithmetic on three numbers, and once you see the pattern you can do it in your head. This guide covers the exact math, the JavaScript that ships in production, alpha channels, and the mistakes that quietly break output.
TL;DR
- Convert each RGB channel (0–255) to a two-digit hex pair (00–FF), then join them behind a
#. rgb(79, 70, 229)→#4F46E5; each channel is one byte, so six digits total.- In JavaScript:
((r << 16) + (g << 8) + b).toString(16).padStart(6, '0'). - An eighth-and-seventh digit pair encodes alpha:
#RRGGBBAA, from CSS Color 4. - RGB and HEX describe the identical sRGB color — the conversion is lossless.
How to convert RGB to HEX by hand
A hex color code is three bytes written in base-16. Each of the red, green, and blue channels holds a value from 0 to 255, and any number in that range fits in exactly two hexadecimal digits. Per MDN's <hex-color> reference, the string always begins with # and is case-insensitive, so #4f46e5 and #4F46E5 are the same color.
The two-digit rule for each channel
To convert one channel, divide the value by 16. The quotient is the first hex digit, the remainder is the second. Digits above 9 use letters: 10 is A, 11 is B, up to 15 which is F. Take the indigo rgb(79, 70, 229):
- 79 → 79 ÷ 16 = 4 remainder 15 →
4F - 70 → 70 ÷ 16 = 4 remainder 6 →
46 - 229 → 229 ÷ 16 = 14 remainder 5 →
E5
Join the pairs and prefix the hash: #4F46E5. There's no rounding and no lookup table — it's the same base conversion you'd do for any number, just capped at two digits because a byte never exceeds 255.
Why 255 becomes FF and not 100
The largest value a single channel holds is 255, which is FF in hex (15 × 16 + 15). The smallest is 0, written 00. That's the whole range: 00 to FF maps cleanly onto 0 to 255, which is why an 8-bit channel and a two-digit hex pair are a perfect fit. If you ever compute a hex pair longer than two digits, a channel has overflowed past 255 and the input is wrong.
A worked second example
Take the yellow-green rgb(154, 205, 50). 154 is 9A (9 × 16 + 10), 205 is CD (12 × 16 + 13), and 50 is 32 (3 × 16 + 2). The result is #9ACD32. Do a handful of these and the common values start to stick — FF, 80 (128, the midpoint), 00, CC, 33 — until most conversions become recall rather than arithmetic.
How to convert RGB to HEX in JavaScript
For anything beyond a one-off, let code handle it. There are two idiomatic approaches, and both appear constantly in production utilities.
The bitwise one-liner
The compact version packs all three channels into a single integer using bit shifts, then formats it as hex:
const rgbToHex = (r, g, b) =>
'#' + ((r << 16) + (g << 8) + b)
.toString(16)
.padStart(6, '0');
rgbToHex(79, 70, 229); // "#4f46e5"
Shifting red left by 16 bits and green by 8 lays the three bytes side by side in one number. Number.prototype.toString(16) renders it in base-16, and padStart(6, '0') restores any leading zeros the numeric conversion dropped — critical for a color like rgb(0, 0, 5), which is 000005, not 5.
The readable per-channel version
If the clever version makes reviewers squint, map each channel independently:
const toHex = n =>
n.toString(16).padStart(2, '0');
const rgbToHex = (r, g, b) =>
'#' + [r, g, b].map(toHex).join('');
This is easier to extend — clamp values, round floats, or append an alpha byte — without re-deriving bit math. Both versions produce lowercase output; wrap the result in .toUpperCase() if your style guide prefers #4F46E5.
When the input isn't clean integers
Real RGB values arrive as floats from a color picker, as strings from a CSS string, or occasionally out of the 0–255 range. Clamp and round before converting:
const clamp = n =>
Math.max(0, Math.min(255, Math.round(n)));
Skipping this step is the single most common source of bad hex output — a value of 255.6 becomes Math.round → 256 → 100, a three-digit pair that corrupts the string.
RGB to HEX with an alpha channel
Opacity gets its own pair of digits. CSS Color Module Level 4 defines the eight-digit #RRGGBBAA form, where the final pair encodes alpha from 00 (fully transparent) to FF (fully opaque). The first six digits are read exactly as before.
Converting an alpha value from 0–1
CSS rgba() expresses alpha as a fraction from 0 to 1, so multiply by 255 and convert. Half-opacity 0.5 becomes 0.5 × 255 ≈ 128 → 80. So rgba(79, 70, 229, 0.5) is #4F46E580. A fully opaque color can drop the alpha pair entirely — #4F46E5FF and #4F46E5 render the same.
The four-digit shorthand
Just as #RGB expands to #RRGGBB by doubling each digit, #RGBA expands to #RRGGBBAA. So #F807 means #FF880077. The shorthand only works when both digits in every channel are identical, which is why it can represent a limited slice of colors — useful for quick prototypes, not for exact brand values.
Alpha support and gotchas
The 4- and 8-digit hex formats work across all current major browsers. The one thing to watch: #RRGGBBAA puts alpha last, while the CSS rgba() function puts it last too but as a 0–1 float — mixing the two mental models is how a 77 alpha (about 47%) gets mistyped as 0.77. Keep the byte-vs-fraction distinction clear.
RGB, HEX, and the color they share
It's worth being precise about what conversion does and doesn't change, because a lot of confusion comes from expecting hex to be a different kind of color.
Same color space, different base
Both notations describe a color in the sRGB space. As the CSS color values guide on MDN lays out, hex, rgb(), and hsl() are all just different syntaxes for the same underlying model. Converting RGB to hex is a base-10-to-base-16 rewrite of three numbers — the pixel that lands on screen is byte-for-byte identical. Nothing is lost, nothing is gained.
What you gain and lose in practice
The trade-off is ergonomic, not visual:
| Format | Best for | Weak spot |
|---|---|---|
#RRGGBB |
Compact, copy-paste, CSS files | Hard to eyeball or nudge a channel |
rgb(r g b) |
Readable, easy to tweak one channel | Verbose, four tokens to copy |
#RRGGBBAA |
Opacity in one token | Alpha math is non-obvious |
Designers usually think in RGB or HSL because the channels are legible; codebases lean on hex because it's terse and unambiguous in a stylesheet. A converter just removes the friction of moving between them.
When to prefer HSL instead
If you're building a palette or tint scale rather than storing a fixed value, converting straight to hex can be premature — HSL or OKLCH make lightness and saturation adjustments far easier. Convert to hex last, once the color is final. For that kind of work, a color palette generator or a gradient generator keeps you in an editable space until you export.
Common RGB to HEX mistakes
A few errors show up again and again in conversion code and manual edits:
- Dropping leading zeros.
rgb(0, 0, 5)is#000005. NumerictoString(16)yields5; withoutpadStartthe whole string shifts and the color is wrong. - Forgetting to clamp. Values above 255 or below 0 produce malformed pairs. Round and clamp before converting.
- Mixing alpha models.
#RRGGBBAAalpha is a byte (00–FF);rgba()alpha is a fraction (0–1). Convert deliberately. - Assuming case matters. Hex is case-insensitive.
#4f46e5equals#4F46E5; pick one for consistency, but neither is more correct. - Expecting a color shift. RGB and hex are the same sRGB value. If the color looks different after conversion, the bug is elsewhere — usually an alpha channel or a color-space assumption.
The safest path for anything user-facing is to convert with tested code or a dedicated tool rather than hand-editing hex strings, where a single transposed digit silently ships the wrong color. iKit's color picker and converter runs entirely in the browser — paste an RGB value, read the hex, and nothing touches a server.
References
- MDN:
<hex-color>CSS data type — hex notation rules,#prefix, case-insensitivity, and the 3/4/6/8-digit forms. - W3C CSS Color Module Level 4 — the current spec for
#RRGGBBAAeight-digit hex and alpha encoding. - MDN: CSS color values guide — how hex,
rgb(), andhsl()are alternate syntaxes for the same sRGB color. - MDN:
Number.prototype.toString()— the radix argument used for base-16 conversion in the code samples.
Related on iKit
- HEX to RGB works the same way in reverse — split into pairs and convert base-16 back to base-10 — the exact inverse of this guide, with the shorthand-expansion rule and the by-hand math for going the other direction.
- Build a full brand palette instead of converting one color at a time — when you need a tonal scale rather than a single hex value, generate the whole ramp and export every stop at once.
Related posts
WCAG Contrast Ratios: How AA and AAA Are Calculated (2026)
A developer's guide to the WCAG contrast ratio formula, the 4.5:1 AA and 7:1 AAA thresholds, large-text exceptions, and how to check color contrast in code.
OKLCH in CSS: A Practical Guide for Developers (2026)
OKLCH in CSS gives you perceptually uniform color, wider P3 gamut, and predictable lightness. Here is the syntax, the math, and the fallbacks for 2026.
Why str.length Lies About Emoji in JavaScript (2026)
In JavaScript str.length counts UTF-16 code units, so one emoji reports 2 and a family emoji reports 11. Here is how to count emoji characters correctly.