Colour converter & contrast checker

Converts between HEX, RGB, HSL, OKLCH and CSS colour names, and checks the WCAG contrast ratio between any two colours.

Foreground
  • HEX#3b82f6
  • RGBrgb(59, 130, 246)
  • HSLhsl(217, 91.2%, 59.8%)
  • OKLCHoklch(62.31% 0.188 259.81)
  • NameNo exact CSS name
Background
  • HEX#ffffff
  • RGBrgb(255, 255, 255)
  • HSLhsl(0, 0%, 100%)
  • OKLCHoklch(100% 0 0)
  • Namewhite
Contrast
3.68:1Contrast ratio
Body text
Below 18pt, or below 14pt bold
AAAAA
Large text
18pt and above, or 14pt bold and above
AAAAA
Controls and graphics
Borders, icons and other non-text parts
AA

Body text at this size is what the 4.5:1 threshold is written for.

Large text needs only 3:1

What this tool does

Two jobs that always turn out to be one. You have a colour and need it in a different notation — the design file says one thing, the stylesheet wants another. And you have two colours and need to know whether text in the first is readable on the second. So this page takes two colours, shows each in every format at once, and measures the contrast between them.

Everything runs in your browser. Nothing about a brand palette that has not shipped yet needs to travel to a server to be converted.

The five notations

Any of these can be pasted in, and all of them come back out:

  • Hex — three, four, six or eight digits. The short forms double each digit, so #f0a means #ff00aa. Four and eight digits carry alpha in the last position.
  • RGB — the three channels as numbers or percentages. Both rgb(59, 130, 246) and rgb(59 130 246 / 50%) are accepted; the comma form is what this tool writes back, because it is still what most code around you uses.
  • HSL — hue in degrees, then saturation and lightness as percentages. Hues wrap, so 480 and -120 are valid and mean 120 and 240.
  • OKLCH — lightness, chroma and hue in a perceptually uniform space. Lightness may be written 62.8% or 0.628.
  • CSS names — all 148 of them, including rebeccapurple and both spellings of every grey. A name is only shown in the output when it matches exactly.

One quirk worth knowing: hex, RGB and OKLCH all survive a round trip unchanged, but HSL does not quite. It is printed at the precision people actually read — whole degrees, one decimal of a percent — and that is not enough to address all 16.7 million sRGB colours. Convert a hex to HSL and back and a channel may move by one. The alternative would be output like hsl(217.22, 91.19%, 59.8%), which nobody wants to paste. The error is far below what an eye can see.

Why OKLCH exists

HSL looks like it describes lightness, and it does not. Its lightness number is a property of the numbers, not of the light. Pure yellow and pure blue are both 50% lightness in HSL:

yellow  #ffff00   hsl 50% lightness    oklch 96.8% lightness
blue    #0000ff   hsl 50% lightness    oklch 45.2% lightness

Anyone can see that yellow is the brighter of the two, and OKLCH says so. This is not academic: it is why a palette built by holding HSL lightness constant and rotating the hue produces a set of colours that look wildly uneven, and why the same trick in OKLCH produces a set that looks like a family. It is also why, in OKLCH, two colours with the same lightness usually have a similar contrast against the same background — something HSL cannot promise at all.

The other two numbers are chroma — roughly how colourful, from 0 for grey — and hue in degrees. Unlike HSL saturation, chroma is not a percentage of anything, which is why the maximum usable value depends on both the lightness and the hue.

When a colour cannot be shown

OKLCH can describe more colours than an ordinary screen can produce. Ask for a lightness of 70% at a chroma of 0.4 and you have named a green that exists in principle and nowhere in sRGB. The tool clamps such a colour into range and marks it as outside sRGB, rather than silently handing back something else.

The warning appears only when the clamp actually changes the colour you get. That distinction matters more than it sounds. sRGB red sits exactly on the gamut boundary, so a published OKLCH value for it, rounded to four decimals, technically lands a hair outside — by about two parts in ten thousand, which lands on the same byte and is invisible. Warning about that would train you to ignore the warning. A colour from a wide-gamut palette misses by whole steps: Tailwind v4 blue-500 asks for 261 of a possible 255 in the blue channel, and on an sRGB screen it genuinely is not the colour requested.

Contrast, and the three thresholds

The contrast ratio compares the relative luminance of two colours. It runs from 1:1 for two identical colours to 21:1 for black on white, and nothing else can beat that. WCAG 2.1 sets three thresholds, and they are not interchangeable:

  • 4.5:1 for body text — anything below 18pt, or below 14pt when bold. This is the one people mean when they say a colour "fails contrast".
  • 3:1 for large text — 18pt and above, or 14pt bold and above. Larger letterforms carry more of the signal themselves, so the bar is lower.
  • 3:1 for controls and graphics — the border of an input, the arrow in a select, the bars of a chart. Added in WCAG 2.1 and widely missed, because people check their text and never check the box around it.

AAA raises the first two to 7:1 and 4.5:1. It is worth aiming for in body copy and genuinely hard to hit in a brand palette; AA is what regulations and audits ask for.

The point sizes in those rules are CSS points, not pixels: 18pt is 24px and 14pt is about 18.7px at default settings. It is easy to assume a 20px heading counts as large text. It does not.

Translucent colours

Half-transparent text on a solid background has a perfectly well-defined contrast ratio: the browser mixes the two colours before painting, and so does this tool. Grey text at 60% opacity is simply a lighter grey once it lands, and it is measured as what it lands as. Tools that ignore the alpha channel report the ratio of the colour you wrote rather than the colour anyone sees, which always overstates it — an accessibility checker that errs in your favour is worse than none.

A translucent background is a different situation, and the tool refuses it. What shows through depends on whatever is underneath, which is not something the two colours on this page can answer. Resolve the background to the colour it actually composites to and measure that.

What the ratio does not tell you

A passing ratio is a floor, not a verdict. Three things it does not cover:

  • Colour blindness. Red and green of identical luminance have a contrast ratio near 1:1 and are indistinguishable to everyone — but two colours can pass the ratio and still be the only thing separating two states, which fails for anyone who cannot tell them apart. Never carry meaning in hue alone.
  • Thin type. The formula knows nothing about stroke weight. A hairline weight at 4.5:1 can be harder to read than a regular weight at 4:1.
  • The formula itself. WCAG 2 contrast is known to be unkind to light text on dark backgrounds, reporting ratios that feel higher than they read. APCA, drafted for WCAG 3, models this better — but it is not normative, no audit accepts it yet, and showing two disagreeing scores would help nobody. That is why it is discussed here and not printed above.

One implementation note for anyone comparing results with their own code: the luminance formula uses a threshold of 0.03928, which differs very slightly from the 0.04045 in the current sRGB definition. WCAG cites the older value, so that is the value used here. Matching the specification matters more than being internally tidy, and the difference never changes a pass into a fail.

Frequently asked questions

Why does my hex change when I convert it to HSL and back?
Because HSL is printed at a precision people can read and retype, which cannot name every sRGB colour. At most one step per channel moves, which is invisible. Hex, RGB and OKLCH all round-trip exactly, so use one of those if you need a lossless intermediate.
What does "outside sRGB" mean?
The OKLCH value you entered names a colour your screen cannot produce, so it has been clamped to the nearest one it can. Values taken from wide-gamut palettes such as Tailwind v4 do this routinely. The warning only appears when the clamp changes the rendered colour, so a rounded value that still lands on the same byte will not raise it.
My text passes AA. Is it accessible?
It clears one specific bar. The ratio says nothing about whether the colour is the only thing distinguishing two states, about stroke weight, or about how the pairing reads for someone with low vision. Treat a pass as the beginning of the check, not the end of it.
Why is my 20px heading not large text?
The rule is written in points, not pixels: 18pt is 24px, and 14pt bold is about 18.7px. A 20px regular heading is below the line and is measured against 4.5:1 like any other body text.
Can I check contrast for a semi-transparent background?
Not from two colours alone — the answer depends on what is behind it, which the page does not know. Work out the colour the background composites to and enter that. A semi-transparent foreground is fine and is handled correctly: it is composited over the background before measuring, exactly as the browser paints it.
Is my palette sent anywhere?
No. Parsing, conversion and the contrast calculation all run in your browser; nothing you type leaves your device.