Accessibility
This page covers color contrast. For assistive-technology semantics — how a
screen reader perceives a fragmented <mark> versus the highlighted text
around it, and how to announce match count/position during keyboard
navigation — see <AccessibleHighlight>,
<MatchAnnouncer>, and
useRovingMatchFocus from the
one-more-highlight/a11y and one-more-highlight/navigation subpaths. The
default <Highlight> from 'one-more-highlight' gives every consumer a
correct <mark> (or role="mark" on a non-semantic highlightTag); the
/a11y subpath is for consumers who need more control over how that reads
to AT.
The default colors used across our playground and documentation examples are WCAG 2.x AAA contrast-compliant (≥ 7:1 for normal text). The full table is documented at the top of docs/site/src/css/custom.css and mirrored in the playground stylesheet — the lowest pair is 9.99 : 1 (text on the pink chip), and most pairs exceed 14 : 1. Both light and dark themes are covered: chip text is pinned to the same dark foreground in both modes, so highlight ratios stay identical regardless of theme.
The library itself ships zero CSS — every color you see in the demos comes from CSS custom properties (--hl-yellow, --hl-green, etc.) that you override in your own stylesheet. If you stick with the defaults, you inherit the AAA palette. If you swap in your brand colors, the AAA guarantee no longer applies to your build, and you should verify the new combinations.
Verifying your own palette
Three categories of tooling, from quickest to most thorough:
1. Online contrast checker — WebAIM Contrast Checker
The fastest sanity check. Paste your foreground hex and background hex and it returns the contrast ratio along with AA/AAA pass/fail for normal text and large text.
- Tool: webaim.org/resources/contrastchecker
- When to use it: picking colors during design, spot-checking a single pair.
- Output: numeric ratio plus four pass/fail labels (AA normal, AA large, AAA normal, AAA large).
Repeat for every highlight color against the text color you plan to render on top of it. For multi-state setups, also check overlap colors if your states layer.
2. Automated testing — axe-core
Run a full WCAG audit (color contrast plus dozens of other rules) as part of your test suite. Maintained by Deque Systems; the same engine powers Lighthouse's accessibility audits, the axe DevTools browser extension, and jest-axe.
pnpm add -D axe-core @axe-core/react
In a React app, the runtime helper logs violations to the console during development:
import React from 'react';
import ReactDOM from 'react-dom';
if (process.env.NODE_ENV !== 'production') {
const axe = await import('@axe-core/react');
axe.default(React, ReactDOM, 1000);
}
For unit tests, jest-axe and axe-playwright wrap axe-core for assertion-style usage:
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('highlight passes axe', async () => {
const { container } = render(<Highlight text="..." searchWords={['x']} />);
expect(await axe(container)).toHaveNoViolations();
});
- Tool:
axe-coreon npm - When to use it: every PR, on every component that renders user-visible UI.
- Coverage: roughly 57% of WCAG issues catchable automatically — contrast, ARIA misuse, missing labels, focus order.
3. Site-level accessibility widget — accessibility (with a caveat)
Floating-icon "accessibility widgets" inject a sidebar that lets visitors toggle high-contrast mode, larger text, link underlines, and similar adjustments at runtime. The accessibility package is the most-downloaded option in this space:
pnpm add accessibility
import { Accessibility } from 'accessibility/dist/main';
new Accessibility();
The accessibility community is broadly skeptical of "overlay" widgets. They can help users with specific motor or vision preferences who don't already use OS-level assistive tech, but they do not fix underlying WCAG violations — and several popular overlays have themselves failed WCAG audits. Treat them as a small convenience for end users, not a compliance shortcut. The single best thing you can do for accessibility is ship semantic HTML and pass an axe-core audit; the widget is optional on top of that.
For background, see Adrian Roselli's Overlay Fact Sheet — a consolidated, vendor-neutral writeup signed by dozens of accessibility consultants.
- Tool:
accessibilityon npm - When to use it: as a small UX nicety, after your markup already passes
axe-core. - What it does: injects a floating button that opens a sidebar of user-adjustable preferences (font size, contrast, grayscale, links highlight, etc.).
Quick checklist for highlight palettes
- Run every
highlight color × text colorpair through WebAIM (target ≥ 7:1 for AAA normal text, or ≥ 4.5:1 for AA). - Verify the same pairs in your dark theme if you use one.
- If you layer states (e.g.
activeoverrides the base color), check the layered combinations too — not just each color in isolation. - Add an
axe-core-based check to CI so future palette tweaks can't silently regress. - Confirm the chosen
highlightTagcarriesmarksemantics. The library addsrole="mark"automatically whenhighlightTagis overridden to a non-semantic element, but a custom render prop that returns a<div>bypasses this — restore the role manually if so.
Which engine, for assistive technology
The DOM engine renders <mark>, whose semantics screen readers understand
everywhere. The CSS engine paints ranges and creates no DOM, so its only
affordance is Highlight.type — announced on Windows and macOS, not yet on
Linux, unknown on Safari. The full matrix is in
CSS Custom Highlights.
Choose deliberately rather than by default: pick the CSS engine for the
performance win on long text when highlights are supplementary, and the DOM
engine — or one-more-highlight/a11y — when a user must not miss the highlight.