ColorDice

RGB vs HEX: Two Ways to Write the Same Colour

If you have ever wondered whether rgb(59, 130, 246) and #3B82F6 produce different results in a browser, the answer is no — they are exactly the same colour. Both notations describe a 24-bit value with one byte for red, one for green, and one for blue. The only difference is the number base: decimal for RGB, hexadecimal for HEX. This page walks through why they are interchangeable, how to convert between them by hand, and which notation fits different situations in CSS, JavaScript, and design tools. If you want to generate colours to experiment with, the random colour generator on this site shows both formats side by side.

The Same 24-Bit Colour Model Under Both Notations

Every colour on a standard screen is stored as three numbers, one each for red, green, and blue. Each number runs from 0 (none of that channel) to 255 (full intensity). That gives 256 possible values per channel and 256 × 256 × 256 = 16,777,216 possible colours in total.

The rgb() CSS function writes those three numbers in base-10, which is the counting system humans use daily. The hex notation writes the same three numbers in base-16, where the digits go 0, 1, 2 … 9, A, B, C, D, E, F before rolling over to 10. Because 256 is exactly 16 × 16, every value from 0 to 255 fits neatly into exactly two hex digits — no rounding, no approximation.

The idea that hex is somehow more precise or more "web-native" than rgb() is a common misconception. Both representations carry identical information. Converting from one to the other and back always returns the original value without any loss. The choice is purely about which format is more convenient for the task at hand.

How to Convert RGB to HEX by Hand

Take each decimal channel value and divide it by 16. The integer quotient becomes the first hex digit; the remainder becomes the second. Map any value 10–15 to the letters A–F respectively.

Working through a real example — rgb(59, 130, 246):

  • Red 59: 59 ÷ 16 = 3 remainder 11. Digit 11 maps to B. Result: 3B
  • Green 130: 130 ÷ 16 = 8 remainder 2. Result: 82
  • Blue 246: 246 ÷ 16 = 15 remainder 6. Digit 15 maps to F. Result: F6

Concatenate the pairs with a leading hash: #3B82F6. You can verify the red channel by reversing the process: 3 × 16 + 11 (B) = 59. This is a well-known blue from Tailwind CSS, and the conversion is exact in both directions.

In JavaScript the same operation is a one-liner: (value).toString(16).padStart(2, '0') for each channel, then join with a hash prefix. Building a full hex string from separate r, g, b values looks like '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('').

How to Convert HEX to RGB by Hand

Strip the leading hash and split the remaining six characters into three two-character pairs. For each pair, multiply the first hex digit by 16 and add the second hex digit.

Converting #3B82F6 back to RGB:

  • 3B: 3 × 16 + 11 (B) = 59
  • 82: 8 × 16 + 2 = 130
  • F6: 15 (F) × 16 + 6 = 246

Result: rgb(59, 130, 246) — exactly the starting value. In JavaScript, parseInt('3B', 16) handles this in one call. Always pass 16 as the second argument. Without it, strings that happen to start with '0' can be silently interpreted as octal in some older environments, producing wrong results with no error.

If you want to pull individual channels from a full hex string programmatically: parseInt(hex.slice(1, 3), 16) for red, parseInt(hex.slice(3, 5), 16) for green, parseInt(hex.slice(5, 7), 16) for blue. You can also use a single bitwise approach: parse the full six-digit hex as one integer with parseInt(hex.slice(1), 16), then right-shift and mask each byte.

When rgb() Is the Better Choice

The rgb() function in CSS accepts a fourth argument for alpha transparency. The older rgba(59, 130, 246, 0.5) syntax and the newer space-separated rgb(59 130 246 / 50%) are equivalent — the latter is simply the modern CSS Color Level 4 syntax. Neither represents a new colour space; both still operate in the same sRGB model. HEX has no equivalent one-liner for alpha, though some tools add two extra digits for an 8-digit hex like #3B82F680.

For programmatic colour work, rgb() pays off immediately. When you need to interpolate between two colours for a gradient, sort a palette by perceived brightness, or generate a range of tints and shades, the individual channel values are already separate numbers you can feed directly into arithmetic. Parsing a hex string adds an extra step every time. The random RGB colour tool on this site demonstrates exactly this — it generates and displays raw channel values because they are the most useful format for programmatic experiments.

In canvas and WebGL contexts you typically pass colour channels as separate floats in the 0–1 range, so rgb()/255 is a natural bridge. Hex requires parsing before you can do any of that work.

When HEX Is the Better Choice

For static colour values in stylesheets, hex is compact and unambiguous. Most design tools — Figma, Sketch, and similar applications — default to hex in their colour pickers, so copying a brand colour from a design file usually gives you a hex string. Using that value directly in CSS without conversion eliminates one possible source of transcription error.

The three-digit shorthand is another practical advantage. #F00 is valid CSS for #FF0000 and saves a few characters in frequently used values. This only works when both hex digits of a channel are identical, but for common palette colours it comes up often enough to be worth knowing.

Hex also works well in SVG attributes, HTML inline styles, CSS custom properties, and anywhere a colour string needs to be written compactly. The six-character format has been supported in every browser since the earliest web standards and carries zero compatibility risk. For the same reason, hex colours appear throughout the hex colour codes reference on this site, which covers the full range of named and numeric hex values.

Modern CSS Colour Syntax: Space-Separated rgb()

CSS Color Level 4 introduced a new syntax for rgb() that drops the commas and uses a forward slash for alpha: rgb(59 130 246 / 50%). This is not a new colour space — it is the same sRGB model with a cleaner syntax. The older comma syntax rgba(59, 130, 246, 0.5) and the new slash syntax produce identical results and both are supported in all current browsers.

The motivation behind the change was consistency: other CSS colour functions like hsl() and oklch() also use the space-separated slash-for-alpha pattern, so switching rgb() to match makes the language more uniform. When writing new code targeting modern browsers, the new syntax is fine to use. For codebases that need to support older browsers, stick with the comma syntax or check your specific target environment.

Neither the old nor the new rgb() syntax has any relation to newer wide-gamut colour spaces like Display P3 or Rec. 2020. Those require the color() function with an explicit colour space argument. Standard rgb() and hex both operate in sRGB regardless of syntax.

Common Mistakes When Working With Both Formats

One frequent mistake is treating the two formats as domain-specific — using hex "for the web" and rgb "for code." Both formats work equally well in any context. You can use rgb() in SVG files. You can use hex strings in JavaScript. The format should follow convenience, not a false rule about where each belongs.

Another common pitfall is mixing percentage and absolute value syntax in rgb(). The notation rgb(100%, 0%, 0%) is valid CSS and equivalent to rgb(255, 0, 0), but you cannot mix the two in the same function call. rgb(100%, 0, 0) is invalid. Pick one style per value and do not mix them.

In JavaScript, forgetting the radix argument to parseInt is a subtle bug. parseInt('09', 16) returns 9 correctly, but in environments that default to octal for strings with leading zeros, parseInt('09') without a radix might return 0. Always write parseInt(hexStr, 16) for hex conversion.

Finally, some developers assume that the 8-digit hex format (#RRGGBBAA) is widely supported and equivalent to rgba(). The 8-digit format is part of CSS Color Level 4, but its support is slightly newer than the four-argument rgba() function. If you need alpha transparency in hex form, check your target environments before relying on it. You can explore colour generation and formats interactively with the hex colour generator to see how values translate between representations.

Choosing a Convention for Your Team

When a codebase mixes rgb() and hex for static colours without reason, pull requests accumulate formatting-only changes that obscure real differences in review. Agreeing on a single convention for static colours removes that noise.

A practical split used by many teams: hex for static design tokens and brand colours that come from a design file, and rgb() or hsl() for values that require alpha transparency or programmatic manipulation. This keeps the "copy from Figma" workflow friction-free while giving dynamic use cases the format that suits them.

CSS custom properties work with both formats, so you can define --color-primary: #3B82F6; in one file and reference it everywhere without conversion. If you need the raw channels elsewhere — for example, to construct a semi-transparent overlay — store the channels separately as --color-primary-r: 59; and compose with rgb() at the call site. This avoids repeating the hex parse logic in multiple places. For random colour generation and experimentation across these formats, try the random colour picker which outputs values in multiple notations at once.

RGB and HEX Conversion in Real Design Handoff Workflows

In a typical product team, designers work in Figma or Sketch and developers implement in CSS or Tailwind. Both tools display colours in hex by default, but the moment a developer needs to add transparency or animate a channel, they must convert. Understanding where and how that conversion happens prevents the most common class of colour bugs: a value that looks right on screen but is subtly wrong in code.

Figma exports hex values such as #1A73E8. A developer who copies this directly into a CSS custom property has no further work to do for an opaque colour. But if the same blue needs to appear at 60% opacity over a light background, the developer must either reach for rgba(26, 115, 232, 0.6) or the eight-digit hex shorthand #1A73E899. That conversion is where mistakes enter: copying the wrong channel order, misreading a hex digit, or forgetting to confirm which of the three channels maps to red versus blue. A shared snippet library with both forms for every brand colour removes the step entirely and keeps designers and developers looking at the same number.

Tokens-based design systems solve this more formally. The token for a primary button might be defined as color.action.primary = #1A73E8 at the design layer and then emitted as a CSS custom property, an iOS UIColor constant, and an Android color resource from a single source of truth. In that pipeline the hex-to-rgb translation happens once — inside the token generation script — and the output for each platform uses whatever notation that platform prefers. Developers never type a channel value by hand.

For teams that have not yet adopted a token pipeline, the most effective low-friction practice is a shared design file comment or a constants file that records hex and rgb forms side by side:

/* brand colours */
--blue-500-hex: #1A73E8;          /* for static, opaque use */
--blue-500-r: 26;
--blue-500-g: 115;
--blue-500-b: 232;                /* for alpha / animation */

/* usage */
background: var(--blue-500-hex);
border: 1px solid rgb(var(--blue-500-r) var(--blue-500-g) var(--blue-500-b) / 20%);

This pattern, sometimes called "split channels," allows a designer to update a single hex token in one file and have every semi-transparent tint, overlay, and animated variant pick up the change automatically. It removes the manual hex-to-rgb recalculation that would otherwise be required every time a brand colour shifts slightly during a refresh cycle.

During a design review, one helpful check is to paste the final hex code into a converter tool and read back the rgb values aloud before signing off. That ten-second step catches transposition errors — swapping the green and blue channels is a common source of slightly purple blues that pass a quick visual scan but fail a colour-contrast audit. Tools like the hex colour generator let you inspect a colour's full breakdown before it enters a codebase.

CSS rgb() vs Eight-Digit HEX: Transparency and Alpha Comparison

CSS offers two syntaxes for adding transparency to a colour: the rgba() function that most developers learned first, and the eight-digit hex notation that was standardised in CSS Color Level 4. Both describe the same thing — a colour with a partial alpha channel — but they differ in how the alpha value is expressed and in which browsers and tools support them.

The rgba() function takes a fourth argument between 0 and 1, where 0 is fully transparent and 1 is fully opaque. The modern rgb() function (no separate "rgba" keyword required in current browsers) accepts a slash-separated alpha using either a decimal or a percentage:

/* legacy syntax — still widely used */
background: rgba(59, 130, 246, 0.5);

/* modern syntax — CSS Color Level 4 */
background: rgb(59 130 246 / 50%);
background: rgb(59 130 246 / 0.5);

/* eight-digit hex — same result */
background: #3B82F680;

In the eight-digit hex form, the fifth and sixth characters are a second hex pair representing the alpha channel on a 0–255 scale, where FF is fully opaque and 00 is fully transparent. To convert a decimal alpha like 0.5 to a hex pair: multiply by 255 to get 127.5, round to 128, then convert to hex — 128 ÷ 16 = 8 remainder 0, giving 80. So 50% opacity on #3B82F6 becomes #3B82F680.

Common alpha hex values are worth memorising or keeping in a reference: 100% opacity is FF, 90% is E6, 75% is BF, 50% is 80, 25% is 40, and 10% is 1A. Design tools like Figma typically express opacity as a percentage in the UI, so developers who work closely with design files may find the eight-digit hex more natural: the opacity percentage from the Figma panel maps directly to a hex pair without any mental arithmetic.

Browser support for eight-digit hex is broad in all modern engines — Chrome, Firefox, Safari, and Edge have supported it for several years. The format is not supported in Internet Explorer, but that browser is no longer a deployment target for the vast majority of projects. For teams that still need IE compatibility, rgba() or the legacy four-digit hex shorthand (where applicable) remain the safe choice.

From a tooling perspective, CSS preprocessors like Sass have long offered colour functions such as rgba($blue, 0.5) that accept a hex variable and emit an rgba() call at compile time. PostCSS plugins can similarly transform eight-digit hex at build time for older targets. If your build pipeline already handles this transformation, choosing between rgba() and eight-digit hex becomes a style preference rather than a compatibility concern.

One practical consideration: some third-party services that parse CSS colour strings — email clients, certain PDF renderers, SVG export tools — do not support eight-digit hex even if browsers do. For maximum compatibility in embedded contexts, rgba() remains the safer choice. In a standard web or app context, both forms are equivalent and the choice comes down to team convention.

Frequently Asked Questions

Is there any colour difference between RGB and HEX?+

No. Both notations encode the exact same 24-bit RGB colour model, which stores one byte (0–255) for each of red, green, and blue. HEX is simply those three decimal values written in base-16 instead of base-10. There is no rounding, no compression, and no precision loss in either direction. A colour written as rgb(59, 130, 246) and one written as #3B82F6 are byte-for-byte identical to every browser, design tool, and image renderer that reads them.

How do I convert a decimal RGB value to HEX manually?+

Divide the decimal value by 16. The quotient (integer part) maps to the first hex digit; the remainder maps to the second. Digits 10–15 become A–F. For example, 130 ÷ 16 = 8 remainder 2, so 130 becomes 82 in hex. Work through all three channels and concatenate with a leading hash: rgb(59, 130, 246) becomes #3B82F6 because 59 = 3×16+11 → 3B, 130 = 8×16+2 → 82, and 246 = 15×16+6 → F6.

How do I convert a HEX colour back to RGB?+

Split the six-character code into three two-character pairs, then convert each pair from base-16 to base-10. The first hex digit is multiplied by 16 and added to the second. For #3B82F6: 3B → 3×16 + 11 = 59; 82 → 8×16 + 2 = 130; F6 → 15×16 + 6 = 246. Result: rgb(59, 130, 246). In JavaScript, parseInt('3B', 16) does this conversion in one call — always pass the second argument 16 or you risk incorrect results when the string starts with a digit like 0.

When should I use rgb() instead of hex in CSS?+

Use rgb() when you need an alpha channel — rgba(59, 130, 246, 0.5) or the modern rgb(59 130 246 / 50%) syntax — or when you plan to compute values at runtime. If you are building a CSS custom property that blends two colours, doing a lightness calculation, or animating a channel value in JavaScript, having the components already as separate numbers is much more convenient than parsing a hex string first.

Can I use HEX colours in JavaScript?+

Yes. HEX strings work wherever CSS colour strings are accepted, including canvas's fillStyle, SVG fill attributes, and any CSS property set via JavaScript. For arithmetic on individual channels you need to parse the string first: parseInt(hex.slice(1, 3), 16) gives you the red channel. If you are doing a lot of colour math, many teams keep colours in objects like { r, g, b } internally and only format to hex or rgb() when writing to the DOM.

What is the three-digit HEX shorthand and when does it work?+

A three-digit hex code like #F06 is a shorthand where each digit is doubled: #FF0066. It only works when both hex digits of every channel are identical — so #3B82F6 cannot be shortened, but #FF0066 can become #F06. The shorthand saves a few characters in CSS and is useful for pure primaries and common palette colours. Most design tools and minifiers expand shorthand to six digits automatically, so there is no risk of inconsistency in production.

Should my team standardise on RGB or HEX?+

Either works, but consistency within a codebase matters more than the choice itself. Most design systems and style guides use hex for static brand colours because it is compact and familiar. Use rgb() or hsl() for dynamic values that need alpha or programmatic manipulation. Mixing the two notations for the same colour in different files creates unnecessary diff noise during code review without adding any real value. Pick one convention for static colours, document it, and stick to it.

What is the eight-digit HEX format and how does it encode transparency?+

Eight-digit hex extends the standard six-digit format with a seventh and eighth character that represent the alpha channel as a hex pair on a 0–255 scale. FF is fully opaque and 00 is fully transparent. To convert a percentage opacity to a hex pair, multiply by 255, round to the nearest integer, and convert that number to base-16. For example, 50% opacity: 0.5 × 255 = 127.5 → rounds to 128 → 128 in hex is 80. So #3B82F6 at 50% opacity becomes #3B82F680. This format is supported in all modern browsers and is particularly convenient when working with design tools like Figma, which display opacity as a percentage that maps directly to a hex alpha pair.

How do design handoff tools handle RGB versus HEX differences?+

Most design tools such as Figma export colours as hex by default, since that is the most compact notation and matches what most CSS codebases use for static values. When a developer needs a semi-transparent version of a brand colour, they must convert the hex to rgb() and add an alpha value — this conversion step is where colour errors most often occur. The safest workflow is to store both the hex value and the individual rgb channel values in a shared constants file or design token, so developers never need to manually derive rgb from hex during implementation. Token-based design systems automate this conversion once at token generation time.