WCAG Contrast Requirements by UI Element
WCAG does not give one contrast number for a whole interface. Normal body text needs 4.5:1 for AA, large text such as headings needs 3:1, and the visible boundaries and states of controls, like a button outline or a focus ring, need 3:1 under the separate non-text contrast rule. Checking every pair against the strictest number rejects colors that comply, and checking a border against the text rows misses the rule that actually covers it.
The element decides the threshold, so this page is organized by element. Large text and headings come first, because that size cutoff is where most review disagreements start. Buttons and component states follow, then dark-mode text, where the dim grays that feel restful fail most often, and finally brand colors, which are usually chosen long before anyone checks them. Each section uses the same checker: enter the foreground and background, then read the Normal Text, Large Text or UI Component row that matches the element.
Run this check yourself in the WCAG Contrast Checker.
Open in the tool →Checking Contrast for Large Text and Headings
A heading styled at 32 pixels can pass WCAG at a contrast ratio that would fail outright as body copy, and that gap trips up more design reviews than almost any other single rule in the guideline. WCAG defines large text as at least 18 point, which works out to 24 CSS pixels at regular weight, or roughly 14 point bold, close to 18.7 pixels, and grants that category a lower bar: 3:1 for AA instead of the 4.5:1 normal text needs, and 4.5:1 for AAA instead of 7:1. Knowing exactly where that size threshold sits, and checking a heading color against the right tier rather than the stricter one meant for paragraphs, is the difference between rejecting a perfectly compliant color and shipping it.
Run this check yourself in the WCAG Contrast Checker.
Open in the tool →Finding the exact pixel size where the lower threshold kicks in
The 18-point and 14-point-bold definitions in the WCAG spec sound abstract until you convert them into the CSS pixel values a browser actually renders, which is where most of the confusion around this rule comes from. Point sizes are a print-era unit, and most designers today think entirely in pixels, so the guideline's own wording needs a translation step before it becomes actionable.
Converting point sizes into the CSS pixels your stylesheet uses
Eighteen point works out to 24 CSS pixels at regular font weight, using the standard 1 point equals 1.333 pixels conversion, while 14 point bold lands close to 18.7 pixels.1 A 24px regular heading and an 18px bold label both qualify for the large-text thresholds, while a 20px regular subheading, a size many design systems use, falls just short and still needs to clear the stricter normal-text bar. That single missing pixel is easy to overlook in a spec review, yet it decides which of two very different ratio requirements actually applies.
That size boundary matters because thicker, taller letterforms genuinely stay legible at lower contrast than the same color pair would at body-text size,1 which is the entire reasoning behind WCAG granting large text a lower bar in the first place rather than treating every visible pixel identically. Treating a headline and a caption as if they carried the same legibility risk would waste real design freedom on the headline for no accessibility benefit.
Bold weight lowers the size threshold, not the contrast requirement
Bumping a heading to bold weight, font-weight: bold or the numeric 700,2 lowers the pixel size needed to qualify as large text, from 24px down to roughly 18.7px, but it does not change the ratio target itself once a piece of text does qualify. It's a useful lever for a compact UI label that needs large-text treatment without taking up as much vertical space as a full 24px line.
A bold 19px label and a regular 24px headline both need the same 3:1 for AA, so weight is a tool for hitting the large-text size threshold at a smaller footprint, not a separate lever on the ratio requirement. Confuse the two and you either reject a compliant bold label for a threshold it never needed to meet, or approve a regular-weight one that actually needed the stricter bar.
Checking a heading color against the right threshold
Type your heading's exact foreground and background into the fields above and read the Large Text row specifically, not the Normal Text row, since the two apply completely different thresholds to the same colors.3 Reading the wrong row is the single most common mistake in a heading-contrast review, and it goes in both directions, rejecting valid colors and approving invalid ones equally often.
A color that fails as body text can be a legitimate heading choice
A muted slate gray like #808a9c against a white background lands around 3.5:1, comfortably clearing the large-text AA bar while falling well short of the 4.5:1 normal text needs.4 Type that exact pair into the fields above and both outcomes appear side by side in the same preview panel, one badge green and the other red for the identical two colors.
That's not a bug in the guideline or a loophole to exploit carefully; it's the intended outcome of a rule built around a real legibility difference between big, bold letterforms and small, dense body copy. Confirm the size and weight of the text you're actually shipping before you approve a color on the strength of the large-text row alone, since a heading style that later gets reused at a smaller size or lighter weight needs to be re-checked against the stricter thresholds.5
When to use this
Use this guide when choosing or auditing a heading, label, or any large-text element, whenever you need to confirm a color against the correct large-text threshold instead of the stricter bar meant for body copy.
Examples
A muted slate heading color that passes the tool's Large Text row but fails its Normal Text row
Foreground #808a9c, background #ffffff
The Large Text (24px Bold) row clears AA at roughly 3.5:1, while the Normal Text (16px) row fails, since both rows render side by side for the same pair and only the large-text threshold is low enough to pass here.
A darker gray that clears both rows, not just the large-text one
Foreground #6b7280, background #ffffff
This pair clears the Normal Text (16px) row at AA as well as the Large Text (24px Bold) row, so it works as a heading or as body copy without needing a separate check for either.
- 1.
W3C, "Understanding Success Criterion 1.4.3: Contrast (Minimum)," w3.org, accessed August 2026. https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
- 2.
MDN Web Docs, "font-weight," developer.mozilla.org, accessed August 2026. https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
- 3.
WebAIM, "Contrast and Color Accessibility," webaim.org, accessed August 2026. https://webaim.org/articles/contrast/
- 4.
W3C, "Web Content Accessibility Guidelines (WCAG) 2.1," w3.org, May 2025. https://www.w3.org/TR/WCAG21/
- 5.
The A11Y Project, "Checklist," a11yproject.com, accessed August 2026. https://www.a11yproject.com/checklist/
18 point regular weight, which converts to 24 CSS pixels, or 14 point bold, which lands close to 18.7 pixels. Text at or above either size and weight combination qualifies for the lower 3:1 AA and 4.5:1 AAA thresholds instead of the stricter normal-text bar.
Not at regular weight; it falls short of the 24px threshold and needs to clear the stricter 4.5:1 normal-text bar. Making that same subheading bold would qualify it, since bold text only needs to reach roughly 18.7px to count as large.
Yes, and this is one of the more commonly misunderstood parts of WCAG. A color scoring between 3:1 and 4.5:1 fails as normal text but is a completely valid, compliant choice for anything that qualifies as large text under the size and weight rules.
Yes. The color pair's ratio never changes, but which threshold applies depends entirely on the final rendered size and weight, so a color approved for a 32px heading needs a fresh check if it later gets reused for an 18px label.
No, since the tool has no way to see your actual CSS. It shows all three rows, normal text, large text, and UI components, side by side so you can read the one that matches the text size and weight you're actually shipping.
Because bigger, bolder letterforms stay legible at lower contrast than small body text does, a real perceptual difference the guideline accounts for rather than applying one strict number to every piece of visible text regardless of size.
Checking Contrast for Dark-Mode UI Text
Dark mode looks effortless until you actually have to pick the gray that text sits on top of, and the wrong choice either burns a faint afterimage into a reader's eyes or fails WCAG outright. The relative luminance formula this tool runs works exactly the same whether your background is near-black or pure white, but designers regularly get dark-mode pairs wrong in a specific, predictable way: choosing a foreground gray dim enough to feel restful, then discovering it barely clears 3:1 instead of the 4.5:1 body text actually needs.1 A near-black background and a near-white foreground, like #121212 against #e0e0e0, is a safe, comfortably passing starting point worth understanding before you start narrowing the gap for aesthetic reasons.
Run this check yourself in the WCAG Contrast Checker.
Open in the tool →Why dark mode tempts you toward failing contrast more than light mode does
Light-mode text almost always defaults toward black on white, a pairing so extreme that most designers would have to work hard to make it fail. Dark mode flips that instinct on its head, because pure white text on pure black can feel harsh and glary, especially in a dim room, so many interfaces deliberately soften both ends toward gray.
The gray-on-gray trap that fails more often than it should
That softening is exactly where trouble starts. A designer who lightens black to a comfortable charcoal and dims white to an easy-on-the-eyes off-white can end up with two colors that sit much closer together in relative luminance than either extreme would, sometimes without realizing how far the ratio dropped. Because both colors moved toward the middle of the brightness range at once, the contrast gap shrinks from both directions simultaneously, which is a faster way to fail than dimming just one side.
A safer approach keeps the background genuinely dark, close to true black, while giving the foreground more room to breathe than it would need against pure white. Material Design's widely used dark-theme baseline of a #121212 background against roughly 87% white text works for exactly this reason:2 the background stays dark enough to avoid glare while the foreground stays light enough to clear AAA with room to spare.
Testing your own dark palette before you commit to it
Type your candidate background and text colors into the fields above and read the Normal Text row specifically, since that is the threshold most of your body copy actually needs to clear. The Large Text row still matters for headings inside the same dark palette, but it is a separate, more forgiving check worth reading on its own.
If the badge shows a fail or a bare AA pass and you'd rather have more headroom, click Fix text to see the closest lighter foreground that reaches AAA without drifting off your original hue, which the tool's OKLab-based search handles automatically,3 letting you keep the same general gray rather than jumping to an unrelated color.
Reading the ratio for an #e0e0e0-on-#121212 pair
Type #e0e0e0 into the foreground field and #121212 into the background field above and the badge clears every WCAG tier by a comfortable margin, including AAA's stricter 7:1 requirement for normal text.4 Swap the two around with the Swap button and the result stays every bit as comfortable, since the ratio treats foreground and background symmetrically.
Why this specific pairing has so much room to spare
That headroom exists because both colors sit near the extremes of the brightness range rather than compromising toward a shared middle gray. The background's near-black luminance and the foreground's near-white luminance stay far enough apart that even a slightly different gray on either side would likely still pass, which gives you real flexibility to warm or cool the palette slightly for brand reasons without risking a fail.
Treat a result this comfortable as a ceiling you can spend, not a target you need to hit exactly, since a slightly softer pairing that still clears AA is often more pleasant to read for long stretches than the maximum-contrast version.5 Use the ratio number above to decide how much of that ceiling you actually want to spend on any given screen.
When to use this
Use this guide whenever you are picking or auditing text colors for a dark-mode interface, and want to confirm a specific foreground and background pair before it ships rather than relying on how restful the pairing looks at a glance.
Examples
A Material Design-style dark card: light text on a near-black background
Foreground #e0e0e0, background #121212
Both colors plug directly into the formula above and clear every WCAG tier, including AAA, since the near-black background and near-white foreground sit far apart in relative luminance.
This is the pairing discussed in the Reading the ratio section above.
A muted gray-on-charcoal pair that looks restful but fails normal text
Foreground #757575, background #1e1e1e
The Normal Text row fails AA here, even though both colors read as comfortably dark and readable to the eye, since dimming the foreground reduced the ratio more than it looks like it should have.
Click Fix text above to see the closest lighter gray that reaches AA without changing hue.
- 1.
W3C, "Understanding Success Criterion 1.4.3: Contrast (Minimum)," w3.org, accessed August 2026. https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
- 2.
Google, "Dark Theme," material-design documentation, github.com, accessed August 2026. https://github.com/zhonghaohu/material-design/blob/master/docs/theming/Dark.md
- 3.
Björn Ottosson, "A Perceptual Color Space for Image Processing," bottosson.github.io, December 2020. https://bottosson.github.io/posts/oklab/
- 4.
W3C, "Web Content Accessibility Guidelines (WCAG) 2.1," w3.org, May 2025. https://www.w3.org/TR/WCAG21/
- 5.
"Dark mode," Wikipedia, accessed August 2026. https://en.wikipedia.org/wiki/Light-on-dark_color_scheme
It's the highest-contrast option, clearing every WCAG tier easily, but many designers find it harsher to read for long stretches than a near-black background paired with an off-white foreground like #e0e0e0, which still clears AAA comfortably while feeling gentler on the eyes.
Softening both the background and the foreground toward a shared middle gray shrinks the contrast gap from both directions at once, which fails faster than most designers expect. Keeping the background close to true black while giving the foreground more room gives you a wider safety margin.
A near-black background around #121212 paired with a foreground near 87% white, such as #e0e0e0, is a widely used baseline that clears AAA with room to spare, giving you flexibility to adjust either color slightly for brand reasons without risking a fail.
Yes. Clicking Fix text or Fix background searches lightness in OKLab space in both directions and returns whichever adjustment reaches your target ratio with the smallest change, regardless of whether the pair you started with was light-on-dark or dark-on-light.
No, and this applies in dark mode exactly as it does in light mode: large text, defined as roughly 24px regular weight or 18.7px bold, only needs 3:1 for AA instead of body text's 4.5:1, so a dark-mode heading can sit closer to its background than a paragraph safely can.
No. CapyToolkit doesn't send the colors you type to a server; the ratio calculation, the Auto-Fix search, and the color blindness simulation all run inside your browser tab, so testing a full dark-mode palette one pair at a time never leaves your machine.
Checking a Brand Color Pair Against WCAG
A brand color usually gets chosen in a logo file or a mood board long before anyone checks whether it can carry body text or button labels without failing accessibility review. That gap between this is our blue and this blue passes WCAG at 16px is exactly where design systems run into trouble months after launch, when a component built around the brand color turns out to fail a contrast audit.1 Testing a candidate brand pair here, before it gets baked into buttons, links, and headings across a whole product, catches the problem while it's still a five-minute color adjustment rather than a re-brand.
Run this check yourself in the WCAG Contrast Checker.
Open in the tool →Why a brand color that looks bold can still fail contrast
Saturated brand colors, the kind that make a logo pop on a business card, often sit in a middle brightness range that neither reads as clearly light nor clearly dark. That middle ground is precisely where contrast failures cluster, because a color that's too bright to work well against white and too dark to work well against black ends up compromised against both.
The saturation trap: vivid does not mean high contrast
A vivid blue like #2563eb looks confident and energetic on a white background, and it does clear AA's 4.5:1 threshold for normal text, but it falls short of AAA's stricter 7:1 bar.2 A lighter, equally on-brand blue like #93c5fd can look almost identical in a logo lockup yet fail even the more forgiving large-text threshold against the same white background, because a small shift in lightness moves the ratio far more than it moves the color's apparent brand identity.
That's the trap: two shades of the same brand hue can sit on opposite sides of a pass or fail line while looking, to a design team eyeballing a mood board, like minor variations on the same color. Checking the exact hex value your team plans to ship, rather than judging the palette by eye, is the only way to know for certain which side of that line you landed on.
Keeping the hue while fixing the ratio
When a brand color fails, the instinct to just pick a completely different color is usually the wrong one, since it throws away the brand recognition the color exists to build. What you actually need is the smallest possible nudge that gets the same color over the line, not a replacement that starts the approval process over from scratch.
Auto-Fix solves the narrower problem instead: it searches for the closest lightness adjustment in OKLab space that reaches your target ratio while holding hue and chroma constant,3 so a failing brand blue comes back as a lighter or darker blue, not a color a brand guideline would reject. That's usually the difference between a fix your brand team signs off on in minutes and one that reopens a debate about the whole palette.
Locking in a pair once it passes, and sharing it with your team
Once a brand pair clears the tier you need, whether that's AA for general use or AAA for a long-form reading context,4 the Share button turns your exact foreground and background into a link anyone on your team can open to see the same result. No screenshot, spreadsheet cell, or verbal description of a color has to stand in for the real, checkable value again.
Documenting the decision instead of re-litigating it later
That link is worth attaching directly to the design system documentation or the ticket where the color got approved, since it saves the next person from re-deriving the ratio from scratch or trusting a screenshot that might not reflect the final hex value.5 Anyone who opens it later sees the live, current result rather than a snapshot that could have drifted out of date.
A shared, bookmarkable link that shows the exact pass or fail state removes the ambiguity that usually creeps in between a color being approved in a design file and it actually shipping in production CSS, and it gives future reviewers a live result instead of a static image that could quietly go stale.
When to use this
Use this guide before a brand color moves from a logo file into production CSS, whenever you need to confirm the exact hex pair clears WCAG rather than approving a palette by eye.
Examples
A vivid brand blue on a white background, checked before it becomes the primary button color
Foreground #2563eb, background #ffffff
The badge shows a clear AA pass for normal text, just short of AAA, giving you a real number to attach to the design decision instead of a guess.
A lighter brand-adjacent blue that looks similar in a logo but fails against white
Foreground #93c5fd, background #ffffff
Every tier fails here, including the more forgiving large-text threshold, which is exactly the kind of near-miss a mood board comparison would never catch.
- 1.
WebAIM, "Contrast and Color Accessibility," webaim.org, accessed August 2026. https://webaim.org/articles/contrast/
- 2.
W3C, "Understanding Success Criterion 1.4.3: Contrast (Minimum)," w3.org, accessed August 2026. https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
- 3.
Björn Ottosson, "A Perceptual Color Space for Image Processing," bottosson.github.io, December 2020. https://bottosson.github.io/posts/oklab/
- 4.
W3C, "Web Content Accessibility Guidelines (WCAG) 2.1," w3.org, May 2025. https://www.w3.org/TR/WCAG21/
- 5.
The A11Y Project, "Checklist," a11yproject.com, accessed August 2026. https://www.a11yproject.com/checklist/
Vivid, saturated colors often sit in a middle brightness range, bright enough to struggle against white and dark enough to struggle against black. A color's apparent boldness in a logo has little to do with where it lands in the relative luminance formula this tool uses.
No. It adjusts only the lightness of the failing color in OKLab space, holding hue and chroma constant, so a failing brand blue comes back as a slightly lighter or darker blue rather than a different color entirely.
Yes, since a color that passes against white can fail against a light gray card background or a colored section, and each combination needs its own check rather than assuming one passing result covers every place the color appears.
Click Share to copy a link that encodes your exact foreground and background into the URL. CapyToolkit doesn't store that link anywhere central, so anyone who opens it sees the same live result, restored straight from the two colors in the address itself.
No. A pair that clears the 4.5:1 normal-text threshold automatically clears the lower 3:1 large-text bar too, but the reverse isn't true, so a color approved only for a large heading can still fail if it later gets reused for body copy.
Text contrast is checked against the 4.5:1 (AA) or 7:1 (AAA) thresholds, while a button border or other UI component boundary only needs 3:1 under success criterion 1.4.11, a lower bar that exists because interface boundaries carry less reading burden than actual text.