Color Contrast Checker
Check text/background contrast against WCAG AA and AAA.
This tool runs entirely in your browser. Your data is never uploaded, never stored, and never leaves your device.
Given a text colour and a background colour, this returns the WCAG a11y contrast ratio between them — 1:1 when the two are identical, 21:1 for black on white — then marks each of the four AA and AAA thresholds pass or fail and draws a sample paragraph in your actual pair.
How to use it
- 1Set Text color and Background color with the pickers, or type hex into the boxes beside them; three-digit shorthand such as #abc is expanded for you.
- 2Read the ratio, then the four badges: AA normal 4.5:1, AA large 3:1, AAA normal 7:1, AAA large 4.5:1.
- 3Check the preview beneath, which renders your pair at 14px regular and 24px semibold.
Example
- Input
- Text #0d6efd on background #ffffff
- Output
- 4.50:1 — passes AA normal, AA large and AAA large; fails AAA normal
That is the stock Bootstrap link blue, and it clears body text by nothing whatever: 4.50 against a threshold of 4.50, so any later nudge to the shade breaks it. Two things about the badges repay knowing. AA normal and AAA large test the same 4.5:1 figure, so those two rows always agree — what differs is the size you are claiming, and WCAG counts text as large only from 18pt (24px), or 14pt when bold. The 3:1 on the AA large row is also the threshold for icons, focus indicators and input borders, so that badge doubles as your non-text check. Only hex is read: rgba() and named colours are refused with "Enter valid hex colors" and the whole result block disappears, so translucent text has to be flattened against what sits behind it before you can test it. Swap colors reverses the preview but never the number, which is symmetric by definition.
What happens to your data
The two hex strings never leave the page. Relative luminance is computed from them here, using the sRGB coefficients and the 0.04045 linearisation step WCAG specifies, and both values are discarded on reload — so an unreleased brand palette pasted in for a quick check is not uploaded, logged or compared against anything remote.
Last updated August 2026
Someone has come back on your design with a note that the grey caption fails, or you are choosing that grey yourself and want to know how far it can be pushed before it turns into an audit finding. A contrast ratio is what settles the argument — one number for a pair of colours, derived from how much light each of them sends back to the eye rather than from how different the two look to you.
The thing to settle before you type anything is which two colours you are genuinely comparing. This measures flat, opaque pairs: the colour the text actually renders as, and the colour of the surface immediately beneath it. Text set at reduced opacity, a label on a tinted card, a caption over a photograph — in all three the hex in your stylesheet is not the colour on the screen, and checking it gives you a figure kinder than reality. Work out the composited colour first, or the worst case where the background varies.
The second thing to settle is which bar you are claiming. Body copy, form labels, error messages and anything a person has to read carefully are judged on the strict threshold. Larger and heavier text is allowed a lower one, but that allowance is defined by rendered size and weight, not by whether the element is called a heading in your markup — promoting a 16px subhead into the relaxed row is the most common way a screenshot that passed becomes a finding that did not.
A pass is a floor, not a verdict. The calculation knows nothing about your typeface, its weight, or the room the screen is in.
How it works
Toolvore reads both hex boxes, expanding three-digit shorthand and lower-casing the result, then converts each colour to relative luminance: every channel is divided by 255, values at or below 0.04045 are divided by 12.92 while the rest go through ((c + 0.055) / 1.055) raised to the power 2.4, and the three results are weighted 0.2126 for red, 0.7152 for green and 0.0722 for blue — which is why a change to the green component moves the figure far more than the same change to blue. The ratio is the higher luminance plus 0.05 over the lower plus 0.05, taken by size rather than by which box you typed the colour into. Two weaknesses are worth knowing. The number is printed to two decimal places while the badges compare the unrounded value, so a pair that displays as 4.50 can still fail the 4.5 rows by a fraction you cannot see on screen. And there is no font size input anywhere: the size rows are yours to interpret, never something the page measures about your design.
Common use cases
- Settling whether a muted grey caption is legible enough to ship
- Choosing the darkest step of a brand colour that still works as body text on white
- Checking a link colour against the page background and against the tinted card behind it
- Answering an accessibility audit finding with a figure rather than an opinion
- Testing a dark theme pair before rebuilding a whole palette around it
- Checking placeholder text, helper text and other low-emphasis states
- Picking the text colour for a coloured button, badge or alert bar
Frequently asked questions
What contrast ratio do I actually need?+
AA is the level almost everything in practice points at — procurement questionnaires, public sector rules in the UK and EU, and most internal design standards all name it, and 4.5:1 for body text is the part of it people quote at each other. AAA sits above that as an enhanced level, and WCAG does not expect a whole site to meet it. It earns its place for long-form reading, sustained data entry, and products used by an older audience, since contrast sensitivity declines with age. Applied everywhere it tends to flatten a palette towards near-black on white, which is why most teams hold AA as the rule and reach for AAA on the screens where reading really matters.
Why does text that passes still look hard to read?+
Because the ratio is a luminance calculation and legibility is not. It takes no account of stroke weight, so a hairline 300 and a medium 500 in the same colour score identically while only one of them survives a laptop screen in daylight. It ignores letter-spacing, line length and any texture behind the text. Two colours of similar lightness but opposing hue can clear the threshold and still vibrate unpleasantly against each other. Very high contrast brings its own trouble: pure black on pure white produces enough glare for some readers that near-black on an off-white ground is the more comfortable choice, even though the number goes down when you make that swap.
How do I fix a failing colour without abandoning the brand shade?+
Change lightness and keep the hue. Most brand colours fail as body text on white because they were chosen for a logo, where nothing has to be read at 14 pixels — so the usual fix is to add a darker step to the palette and reserve the original for surfaces, large headings and icons. Reversing the pair is often easier than darkening: white on the brand colour clears the bar in plenty of cases where the brand colour on white cannot. Yellows, limes and light cyans are the stubborn ones. Their luminance sits close to white whatever you do to saturation, so they work as backgrounds and almost never as text on a light page.
How do you check text sitting on a photo, gradient or translucent layer?+
Flatten it to a single pair before testing. For semi-transparent text, composite each channel — the result is alpha times the text channel plus one minus alpha times the background channel — then convert the three numbers back to hex, since only opaque hex is read here and translucency has to be resolved by you. For a photograph or a gradient there is no single background, so test the worst case: the lightest pixel under dark text, the darkest under light text. Where the image is uploaded by someone else through a CMS, no fixed pair is safe, and the durable fix is a solid or gradient scrim behind the text so the colour beneath it is one you control.
Do icons, borders and logos need to pass as well?+
Meaningful non-text things do: the glyph of an icon that carries information, the border of a text input, a focus ring, chart segments that have to be told apart. Logos and brand names are exempt, which is why a wordmark in a pale tint is not a violation even when it is hard to read. Inactive controls are exempt too, though a disabled button nobody can decipher still generates support tickets. Purely decorative graphics fall outside it entirely. Since only one pair is reported at a time, an icon is checked exactly the way text is: the colour of the glyph against the colour of the surface behind it.
Do I need to check the dark theme separately?+
Yes, and not because the arithmetic changes — reversing a pair leaves the figure identical — but because the palettes do. A dark theme is rarely a straight inversion: a grey ramp spaced to look right on white tends to bunch up against a near-black surface, so the muted greys that passed in the light theme are the first things to fail in the dark one. Perception shifts as well. Light text on a dark ground blooms slightly, which is why designers often pull white back to a light grey and black up to a dark grey rather than running both extremes. The WCAG 2 formula is weakest at the dark end, so treat a marginal dark pass as marginal.
Does passing contrast mean the design works for colour-blind readers?+
No. The calculation reduces each colour to a single luminance figure and discards hue completely, so two colours can score identically against white and be impossible to tell apart from one another. That is a separate requirement: information must never be carried by colour alone. In practice it means underlining links inside body copy, pairing a status colour with an icon or a word, labelling chart series directly instead of relying on a key, and marking an invalid field with a message rather than a red border. Viewing the screen in greyscale for a minute finds most of these problems, and no contrast figure ever will.
Where do the colours go — is anything uploaded or saved?+
Nothing leaves your browser. The two hex values sit in the component's own state, the luminance and the ratio are worked out in the page, and no request carries a colour anywhere — which is the point when the palette you are testing belongs to a rebrand nobody has announced. Nothing is stored either: no account, no history, and nothing written to the address bar, so a result cannot be handed to a colleague as a link, and a reload puts the fields back to the dark indigo on white they start from. If the outcome needs to be on record, screenshot the badges or write the pair and the figure straight into the ticket.
Used in these workflows
Related tools
Placeholder Image Generator
Create custom-size placeholder images with your own text and colors.
Crop Image
Trim a photo to the part you want, freehand or locked to a ratio like 1:1 or 16:9.
CSS clamp() Generator
Build fluid font-size clamp() rules between two viewport widths.
Color Shades Generator
Generate tints and shades of any base color.