# Grey body text under 7:1 contrast

Source: https://easeweb.dev/learn/enhanced-contrast
Topics: Colour and contrast > Text has 7:1 contrast (WCAG 1.4.6)
The fix: Either set body text in a darker grey that reaches 7:1, such as #555555 at 7.46:1, and keep the quiet look with size, weight and space; or put a contrast switch before the content that turns the text dark and keeps the choice on every page. Text from 24px, or 18.66px bold, needs 4.5:1.
Test with: contrast measurement, keyboard, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html https://www.w3.org/WAI/WCAG22/Techniques/general/G17 https://www.w3.org/WAI/WCAG22/Techniques/general/G174 https://webaim.org/resources/contrastchecker/ https://developer.chrome.com/docs/devtools/accessibility/contrast https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A ceramics studio's journal opens an article, "How we glaze a mug", with a near-black headline, a byline and two long paragraphs. The designer wanted a calm, editorial page, so the paragraphs and the byline are set in a soft grey, #767676, and the headline stands out above them. That grey was picked with care: it is the lightest neutral grey that still passes the 4.5:1 minimum on white, and the site passes its AA audit.

A reader with low vision, who reads without a magnifier, finds the headline easy and the paragraphs a strain. The letters are all there, but each line takes effort; a few lines in, they lean towards the screen, or stop reading.

## Why it fails

A contrast ratio compares how light the text is with how light the background behind it is. It runs from 1:1, text the same colour as the page, to 21:1, black on white: the higher the number, the more the letters stand out.

WCAG 1.4.6 asks for at least 7:1 between text and its background, and 4.5:1 for large text: 24px and up, or 18.66px and up when bold. The paragraphs are 17px regular and the byline is 13px, so both are normal text and need 7:1. #767676 on white is 4.54:1. It passes 1.4.3, the AA minimum, and fails 1.4.6. The headline is #1a1a1a, at 17.40:1, and passes either way.

The gap between the two levels is a gap between readers. The W3C's explanation of 1.4.6 says 7:1 makes up for the loss of contrast sensitivity that usually comes with vision loss of about 20/80. People with more loss than that usually use assistive technology, such as magnification and contrast settings. The 7:1 level is for the people in between, who read the page as it is drawn.

Nothing in the browser stands in the way: it draws whatever colour the stylesheet names. The grey is usually a design token called "muted" or "secondary", checked once against 4.5:1, and then used for every paragraph, caption and byline the design wants quiet.

## Who is affected

It matters most on what people read at length: an article, a recipe's method, a product's care instructions, terms and conditions. A short label in the same grey is read once; a page of it is read line after line. The same reader also meets it outdoors on a phone, on a laptop with its brightness turned down, and through glasses that are a little out of date.

## What automation and AI miss

axe has a rule for 7:1, color-contrast-enhanced, but it is tagged Level AAA and off by default, so a scan that runs axe's defaults, or only the A and AA rules, passes #767676 without a word. A design system that checks its palette against 4.5:1 only marks the grey "accessible". An AI tool asked for "an accessible grey" can suggest #767676 or a grey from a library, such as Bootstrap's secondary `#6c757d` (4.69:1) or Tailwind's `gray-500`, `#6b7280` (4.83:1), all short of 7:1. The EaseWeb audit runs color-contrast-enhanced and reports the text that passes 4.5:1 but not 7:1 as Level AAA leads for review.

## Before: body text in the lightest grey that passes AA

```html
<header class="site-header">
  <a href="/" class="logo">Kiln & Clay</a>
</header>
<main>
  <article class="article">
    <h1>How we glaze a mug</h1>
    <p class="byline">Studio journal · 6 min read</p>
    <p>Every mug is dipped twice: once in a pale base glaze, then at the rim in a darker one that runs as it fires.</p>
    …
  </article>
</main>
```

```css
:root {
  --ink: #1a1a1a;   /* 17.40:1 on white */
  --muted: #767676; /* 4.54:1 on white */
}
body {
  background: #fff;
  color: var(--ink);
}
.article p {
  color: var(--muted);
  font-size: 1.0625rem;
  line-height: 1.7;
}
.byline {
  font-size: 0.8125rem;
}
```

The headline is right: near-black on white, at 17.40:1. The paragraphs and the byline are where it fails:

- The paragraphs are 17px #767676 on white, at 4.54:1, where 7:1 is needed.
- The byline is 13px in the same grey, at the same 4.54:1.
- The only thing that sets the body apart from the headline is the lighter grey, so the quiet look and the low contrast are one choice.

## After: a darker grey that reaches 7:1

```html
<header class="site-header">
  <a href="/" class="logo">Kiln & Clay</a>
</header>
<main>
  <article class="article">
    <h1>How we glaze a mug</h1>
    <p class="byline">Studio journal · 6 min read</p>
    <p>Every mug is dipped twice: once in a pale base glaze, then at the rim in a darker one that runs as it fires.</p>
    …
  </article>
</main>
```

```css
:root {
  --ink: #1a1a1a;   /* 17.40:1 on white */
  --muted: #555555; /* 7.46:1 on white */
}
body {
  background: #fff;
  color: var(--ink);
}
.article h1 {
  font-weight: 800;
}
.article p {
  color: var(--muted);
  font-size: 1.0625rem;
  line-height: 1.7;
}
.byline {
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
}
```

1. Change the muted token to #555555, a grey that still reads as grey, at 7.46:1 on white. Every paragraph and byline that uses the token gets it at once.
2. Give the headline a heavier weight, so it still stands out from the darker body.
3. Space the byline's letters a little, so it stays quiet by size and spacing rather than by colour.

This fixes the Before and passes 1.4.6 for every reader, with nothing to switch on.

## After: a contrast switch before the content

```html
<header class="site-header">
  <a href="/" class="logo">Kiln & Clay</a>
  <button type="button" class="contrast-switch" aria-pressed="false">Higher contrast</button>
</header>
<main>
  <article class="article">
    <h1>How we glaze a mug</h1>
    <p class="byline">Studio journal · 6 min read</p>
    <p>Every mug is dipped twice: once in a pale base glaze, then at the rim in a darker one that runs as it fires.</p>
    …
  </article>
</main>
<script>
  const root = document.documentElement;
  const toggle = document.querySelector('.contrast-switch');
  const show = (more) => {
    root.dataset.contrast = more ? 'more' : 'normal';
    toggle.setAttribute('aria-pressed', String(more));
  };
  const saved = localStorage.getItem('contrast');
  show(saved ? saved === 'more' : matchMedia('(prefers-contrast: more)').matches);
  toggle.addEventListener('click', () => {
    const more = toggle.getAttribute('aria-pressed') !== 'true';
    localStorage.setItem('contrast', more ? 'more' : 'normal');
    show(more);
  });
</script>
```

```css
:root {
  --ink: #1a1a1a;   /* 17.40:1 on white */
  --muted: #767676; /* 4.54:1 on white */
}
/* The switch's presentation: every paragraph in the ink. */
:root[data-contrast='more'] {
  --muted: var(--ink);
}
body {
  background: #fff;
  color: var(--ink);
}
.article p {
  color: var(--muted);
  font-size: 1.0625rem;
  line-height: 1.7;
}
.byline {
  font-size: 0.8125rem;
}
.contrast-switch {
  border: 1px solid var(--ink);
  background: #fff;
  color: var(--ink); /* 17.40:1, so the switch itself is easy to read */
}
/* Pressed: filled, so its state shows without colour alone. */
.contrast-switch[aria-pressed='true'] {
  background: var(--ink);
  color: #fff;
}
```

1. Add a "Higher contrast" button to the header, before the main content, so it is among the first things found by eye, by Tab and by a screen reader.
2. Give it `aria-pressed`, so a screen reader says whether it is on.
3. When it is pressed, set `data-contrast="more"` on the root element, which turns the muted token into the ink: the paragraphs go from 4.54:1 to 17.40:1.
4. Keep the choice in `localStorage` and read it on every page, so a reader turns it on once. With nothing saved yet, start from the reader's own `prefers-contrast` setting.
5. Draw the switch itself in the ink, at 17.40:1, and fill it while it is on: a reader who needs it has to be able to read it and see that it is on.

This fixes the Before and passes 1.4.6 for the page with the switch on, which WCAG accepts as a way to meet it (technique G174). The default page is still 4.54:1, so it is not enough on its own: it works only for readers who find the switch, and only while it is on every page and keeps its choice.

## Which option to use

Both pass. Use the darker grey: it reaches every reader with nothing to find or turn on, it is a one-line change to a token, and the page keeps its quiet look through size, weight and spacing. Add the switch only when the lighter grey is a decision the brand won't change, and then treat the switch as part of every page's header, kept in its place and tested on each release.

## Implementation decisions

Large text needs only 4.5:1 under 1.4.6, so a heading of 24px or more, or 18.66px and bold, can stay in #767676. Everything smaller needs 7:1, and that includes the text designers most often make quiet: bylines, captions, helper text under a field, the small print in a footer.

Give the token a name that says what it may be used for, and write its ratio beside it. A token called `--muted` gets used for anything quiet; one called `--text-secondary` with a 7.46:1 comment tells the next person it is safe for paragraphs.

`prefers-contrast: more` is worth honouring, as the switch does when nothing is saved, but on its own it is not a fix for 1.4.6. It only changes the page for people who have turned on a contrast setting on their device, and the page everyone else gets still has to meet the criterion.

The switch reads its choice in a script, so a page can show the grey for a moment before it turns dark. Set `data-contrast` from `localStorage` in a small script in the head, before the body is drawn, or on the server from a cookie.

Every state and every theme counts, as with the AA minimum: hover, focus and visited colours, a dark theme's own greys, and text with `opacity` on it, whose colour is lighter than its token. In a Windows contrast theme the browser replaces these colours with the user's own, so the grey doesn't reach them there, but it still has to pass in the page's own colours.

## Verify the fix

1. Measure every text colour on the page against its background with the DevTools colour picker or the WebAIM checker: the paragraphs, the byline, captions, helper text and the footer. Confirm 7:1, or 4.5:1 for text of 24px or more, or 18.66px and bold.
2. Force `:hover`, `:focus-visible` and `:visited` from the DevTools `:hov` panel, and measure each link and button again.
3. If the fix is a switch: from the top of the page, Tab to it with a screen reader running, press it and hear it reported as pressed, measure the text again, then open another page and reload, and confirm it is still on.
4. Switch to each theme the site offers and measure again, then turn on a Windows contrast theme and confirm the text follows its colours.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the colour tokens or the theme change.

## Limits

This is a starting pattern, not a guarantee for every application. Colour values and token names are examples; measure your own palette on your real backgrounds. Text over photos, and text below the 4.5:1 minimum, are covered in [low-contrast text on brand colours, photos and hover states](/learn/text-contrast). A contrast ratio is a minimum, not proof that text is comfortable to read: thin weights, small sizes and long lines make reading harder at the same ratio, and some readers find full black on white too harsh. Check the complete pages with people who rely on them.

## What screen readers do (tested by hand by EaseWeb)

`aria-pressed`: Makes a button a toggle that is on or off.

| Screen reader | Browser and system | Press it | Tab to it |
| --- | --- | --- | --- |
| JAWS | Chrome, Windows 11 | Not tested | Not tested |
| NVDA | Chrome, Windows 11 | Not tested | Not tested |
| JAWS | Edge, Windows 11 | Not tested | Not tested |
| NVDA | Firefox, Windows 11 | Not tested | Not tested |
| NVDA | Edge, Windows 11 | Not tested | Not tested |
| JAWS | Firefox, Windows 11 | Not tested | Not tested |
| VoiceOver | Safari, macOS | Not tested | Not tested |
| VoiceOver | Safari, iOS | Not tested | Not tested |
| TalkBack | Chrome, Android | Not tested | Not tested |

https://easeweb.dev/learn/screen-reader-support

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
