Skip to content
AldeaCode Logo
Color Converter / RGB to Hex Developer 100% local

RGB to Hex converter: padding, case and the alpha trap

RGB to hex is the inverse of the easier conversion, and the easier conversion was already easy. The bugs come from missing the zero pad on a single digit value and from inventing a case convention that disagrees with your linter.

Three numbers, six characters

An RGB triplet like rgb(15, 136, 250) becomes hex by converting each channel to base 16 and concatenating. 15 is f, 136 is 88, 250 is fa. Stitched together that is f88fa which is wrong, because 15 was supposed to be 0f and not f.

Padding is the rule. Each channel, no matter how small, must produce exactly two characters. 0 becomes 00, 5 becomes 05, 15 becomes 0f. The standard one liner in any language is the equivalent of (n).toString(16).padStart(2, "0"). Skip the pad and one in sixteen valid colours produces a five character hex that no parser accepts.

The other detail is the leading hash. CSS requires it, most parsers accept the hex with or without. Producing the hash by default is friendlier than not, callers who want to drop it can do so.

Lowercase wins by convention

Both #FF8800 and #ff8800 are valid CSS and produce the same colour. The W3C does not pick one. The convention in the wild is lowercase, because most code formatters (Prettier among them) normalise to lowercase, because hex letters next to numbers read better in lowercase, and because design tools (Figma, Sketch, modern Adobe) export lowercase by default.

If your team has a different rule, pick one and lint it. Mixed case in the same stylesheet creates noisy diffs whenever someone touches a colour, because the formatter rewrites it to your convention and the diff balloons. Set the convention once, normalise on the way in.

Alpha and the modern color() syntax

rgba(15, 136, 250, 0.5) becomes #0f88fa80. The fourth pair is the alpha byte, computed as Math.round(alpha * 255).toString(16).padStart(2, "0"). 0.5 becomes 128 becomes 80. 0.8 becomes 204 becomes cc. The same float to byte rounding bites here as in the inverse conversion.

CSS Color Module Level 4 introduces color(srgb 0.06 0.53 0.98 / 0.5) and the new space separated rgb(15 136 250 / 0.5) syntax. They are equivalent, the modern form composes better with relative colour syntax (color-mix, oklch based theming). Hex with eight digits remains the most compact representation and is usually the right output for a converter, the new forms are for when you want to do colour math in CSS rather than freeze the result.

Edges in code: clamp, round, type

The two production bugs in any RGB to hex implementation are unbounded inputs and floats. Designers paste rgb(259, 136, 0) from a tool that did the math wrong, and a naive snippet returns #10388800 (because 259 becomes the hex string 103 which is three chars). A correct snippet clamps to 0 to 255 first.

Floats arrive when an animation library passes interpolated values like 136.7. Math.round before the toString or you get #88.b333... which is not a thing. The snippet below clamps, rounds, pads, and lowercases. Drop it in unchanged.

Working example

javascript
// RGB to hex. Clamps to 0..255, rounds floats, optional alpha 0..1.
function rgbToHex(r, g, b, a = 1) {
  const clamp = (n) => Math.max(0, Math.min(255, Math.round(Number(n) || 0)));
  const pad = (n) => clamp(n).toString(16).padStart(2, "0");

  const hex = "#" + pad(r) + pad(g) + pad(b);

  if (a >= 1) return hex;
  const alpha = Math.max(0, Math.min(1, Number(a) || 0));
  const aHex = Math.round(alpha * 255).toString(16).padStart(2, "0");
  return hex + aHex;
}

// rgbToHex(15, 136, 250)        -> "#0f88fa"
// rgbToHex(15, 136, 250, 0.5)   -> "#0f88fa80"
// rgbToHex(259, -3, 136.7)      -> "#ff0089"  (clamped, rounded)
// rgbToHex(0, 0, 0, 0)          -> "#00000000"

Just need the result?

Paste the rgb() value into the colour converter on aldeacode.com and the hex, the lowercase eight digit form with alpha, and the modern rgb() with slash syntax all appear at once. The math is clamped and rounded, the case is normalised, and the conversion runs entirely in your browser.

Open Color Converter (Hex / RGB / HSL) →

Frequently asked questions

Should the hex output be lowercase or uppercase?

Lowercase by convention. Prettier, ESLint stylelint defaults, and most design tool exports normalise to lowercase. Pick one, lint it, save your team a class of pointless diffs.

What happens if I pass 256 or a negative number?

A correct converter clamps to the valid 0 to 255 range. The snippet here uses Math.max and Math.min before the toString call. A naive implementation produces a three character hex that breaks every parser downstream.

Do I need the alpha as a fourth pair or as separate rgba?

Eight digit hex is now widely supported (Chrome since 2017, Safari since 13). It is the most compact and the most copy paste friendly. Use rgba() syntax when the alpha is dynamic and you want CSS to read clearly.