Styling Links Without Losing Affordance
Custom link color is fine. Custom link affordance is not optional.
Underlines disappeared from the article template during a typography refresh. Link color moved to a soft blue-violet that matched the new accent. Lighthouse scores stayed green. Session duration did not collapse. Support tickets did, specifically tickets asking where to click in help articles and release notes. Readers were not confused because the site was slow or because the hue was ugly. They were confused because nothing in the static state of the paragraph said interactive except a lightness shift many users could not see at fourteen pixels on a laptop in sunlight.
The team had styled links. They had not preserved affordance, the visual cues that tell a reader this text is interactive without forcing hover and hope. Custom link color is a branding exercise. Custom affordance is an accessibility requirement, and the two are not the same problem. Browsers ship defaults for anchors. Authors override with color, text-decoration, and increasingly with design tokens in oklch. The CSS Text Decoration Module gives fine control over underlines, offset, thickness, skip-ink behavior. None of that matters if hover is the only state that signals link in a wall of body copy. Treating navigation chrome and prose references as one selector is how affordance disappears company-wide from a single global reset.
Affordance, WCAG, and what readers actually scan
WCAG 2.2 Success Criterion 1.4.1 Use of Color requires that information conveyed by color alone also be available through another visual indicator. For body links, color alone is rarely sufficient when underlines are removed, especially for users with low vision or color vision deficiency who may not distinguish your link hue from surrounding text. Auditors frequently cite inline links that rely on a slightly different shade of gray or a subtle hue shift. The link looks fine to a designer on a calibrated display. It fails for readers who skim help docs at default zoom.
Affordance has a usability dimension beyond formal compliance. Even when contrast ratios pass, readers skim. Underlines or another unmistakable cue reduce cognitive load. Major publishing systems still underline links in article bodies while navigation chrome stays minimal because landmark context supplies interactivity cues in the header that paragraphs do not inherit. MDN documents the link and visited pseudo-classes as matching unvisited and visited anchors. any-link covers both. Scope rules separately for prose, navigation, cards, and button-styled links. One global anchor reset is the most common mistake in redesign postmortems.
Run prose links through a contrast checker against the background they sit on, not an assumed white page. Articles on tinted surface-muted panels fail differently than articles on pure white. Link tokens must be tested on every surface variant your CMS allows. A token that passes on default background may fail on callout boxes where marketing places the highest density of inline references.
Body links versus navigation chrome
Prose links inside reading contexts need the strongest cues. Help docs, blog posts, legal copy, release notes. Start with tokenized link color, underlines on by default, and visible focus for keyboard users per WCAG 2.4.7 Focus Visible.
:root {
--link: oklch(0.52 0.17 264);
--link-hover: oklch(0.47 0.17 264);
}
.article a:any-link {
color: var(--link);
text-decoration: underline;
text-underline-offset: 0.18em;
text-decoration-thickness: 1px;
}
.article a:hover {
color: var(--link-hover);
}
.article a:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
border-radius: 2px;
}
text-underline-offset pulls the underline away from descenders so g and y remain legible at body sizes. currentColor on the focus ring keeps the ring aligned with link hue without a third hard-coded color. Before this pattern, one team’s article links used text-decoration none and a twelve percent lightness difference from body text. After restoring persistent underlines, support-related click-through on linked phrases in documentation rose measurably, not because the color was louder, but because readers could see links while scanning.
Primary navigation is a different animal. Links sit in a nav landmark with spacing, hover backgrounds, and position that communicates interactivity. Underlines on every nav item look noisy. Many design systems remove them. The failure mode is copying nav rules into article bodies. Teams assume if it works in the header, it works in paragraphs. It does not.
.site-nav a:any-link {
color: inherit;
text-decoration: none;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
}
.site-nav a:hover {
background: color-mix(in oklch, currentColor 8%, transparent);
text-decoration: none;
}
.site-nav a:focus-visible {
outline: 2px solid var(--focus-ring, oklch(0.55 0.18 264));
outline-offset: 2px;
}
color inherit lets nav links track header text across light and dark themes. Hover uses a subtle mixed background rather than hue shift alone. Touch devices may never show hover, so critical information cannot live only in hover color changes. If nav links are only distinguished by bold weight, verify the difference is obvious at a glance. Bold body text adjacent to bold links recreates the prose failure mode inside shorter UI strings.
Hover, focus, touch, and visited states
Hover is not a reliable delivery mechanism for essential information. On touch-first devices, hover may apply inconsistently or only after tap. Link styling that only differentiates on hover is broken for a large fraction of mobile readers, not because they cannot activate links, but because they cannot find them before activation. Safe hover reinforces an already-visible affordance: underline thickens, background appears. Unsafe hover makes gray body text link-colored only on hover, adds underline only on hover in paragraphs, or hides visited distinction until pointer contact.
For pointer users, relative color adjustment on hover is fine when underlines remain. CSS Color Module Level five relative syntax can adjust lightness while holding chroma and hue, useful for consistent hover darkening across themes without maintaining a separate hover token. Browsers without relative color support need explicit fallback variables.
.article a:hover {
color: oklch(from var(--link) calc(l - 0.05) c h);
}
Active styles matter on touch. A brief background or opacity shift confirms tap. Keep active subtle. Do not depend on active for discovery. Visited styling is constrained for privacy. Browsers limit which properties authors may apply to visited links. Color is allowed. Many layout and background properties are not. Product application chrome often omits distinct visited styling when links are actions such as View invoice rather than exploratory web navigation. Long editorial content benefits from visited cues. If you omit them, unvisited links must remain obvious through underline, weight, or iconography, not through a hue difference alone many users cannot perceive.
A conservative visited treatment pairs hue shift with persistent underline. focus-visible rather than focus on links preserves keyboard paths without showing rings on every mouse click. Removing default focus outlines for aesthetics without replacement is a navigation failure auditors catch immediately. Match focus ring color against link text and surrounding backgrounds. A ring that passes on white may vanish on tinted callout boxes.
Case study: the typography refresh that hid help links
A documentation-heavy B2B product shipped a typography refresh driven by marketing’s new brand system. Headings moved to a tighter scale. Body copy lightened one step on the neutral ramp for a more editorial feel. Links inherited the brand accent at reduced chroma so they would not compete with primary buttons. A global stylesheet rule set text-decoration none on all anchors for consistency with the navigation header, which used padded items and background hover states. Nobody scoped the reset. Article templates, changelog entries, and in-app help strings inherited the same rule.
Qualitative testing did not fire immediately. Quantitative metrics looked stable. Support volume told the truth within ten days. Tickets referenced specific help phrases users could not locate as links. Screen recordings showed readers selecting entire paragraphs hunting for clickable text. Color vision deficient users were overrepresented in the ticket tags relative to account base. The link hue differed from body text by roughly eleven percent lightness and a small chroma shift on oklch coordinates tuned for large display type, not fourteen pixel prose inside gray callout panels.
An accessibility review reproduced failures without exotic tooling. WCAG Use of Color applied because interactivity was conveyed primarily by hue. Contrast on links passed on white backgrounds at four point seven to one. On surface-muted callout panels behind most procedural steps, links fell to four point one to one against body text adjacent in the same sentence, a pass for text against background but a practical fail for differentiation. Deuteranopia simulation merged link and body hues into indistinguishable mid-tones. The underline removal was the decisive mistake. Hue alone would have been fragile. Hue alone without underline on a global reset was hostile.
The fix scoped resets instead of reversing the brand palette. Navigation kept text-decoration none with background hover. Article and help containers regained underlines with offset and thickness tuned for the new type scale. Link tokens gained a dedicated prose alias tested on white and muted panels. Focus-visible rings returned with currentColor outlines. Visited styling stayed minimal but underlines persisted for both states. Engineers added a lint rule flagging unscoped anchor text-decoration none in global CSS. Design documented two patterns in the system library: prose link and nav link, with explicit guidance never to reuse nav rules in MDX content.
Post-fix, support tickets on lost links dropped within a release cycle. Internal writers reported faster authoring because links looked like links in the CMS preview without enabling hover. The team measured a modest lift in linked phrase click-through on onboarding articles, consistent with reduced scanning friction rather than louder color. Stakeholders who cared about brand still got accent-driven nav and buttons. Readers who lived in paragraphs got affordance that survived sunlight, color vision variance, and touch-first hardware.
The lesson embedded in the incident was that affordance is contextual. Navigation landmarks trade underlines for spatial layout. Paragraphs do not get that trade for free. A typography refresh that treats all anchors as one component class exports a subtle bug that automated Lighthouse passes miss because contrast math and use-of-color judgment require human context about where the link sits and what cue exists before hover.
Forced colors, email, and shipping discipline
Windows High Contrast and forced-colors mode override author link colors with system palettes. Affordance must survive through underline or system LinkText keyword usage. Test with forced-colors active, not only default macOS Chrome. A prose pattern that depends on a specific brand blue hue will collapse to system LinkText in forced-colors mode. Underlines, borders, or other non-color cues are what remain when author hues disappear. Document that expectation in the design system so stakeholders do not treat forced-colors screenshots as bugs.
Dark mode needs the same scoping discipline as light. Nav inherit patterns track header tokens. Prose underlines remain visible when link hue approaches body hue on charcoal surfaces. Link tokens tuned only on white artboards often converge toward body text on dark surfaces when chroma drops for a softer brand read. The failure is not darkness itself. It is insufficient separation plus missing decoration. A slightly higher chroma on the link token or a persistent underline solves most dark-mode skim failures without returning to default browser blue.
Card components that wrap entire rows in anchor tags blur the boundary between navigation and prose affordance. A dashboard card that is entirely clickable may reasonably drop underline on the title while keeping a chevron icon as redundant encoding. A sentence inside a card that mentions another product by name needs inline link treatment, not card treatment. Mixed patterns inside one template are where global resets do the most damage. Scope link styles to the content container inside the card, not only to the card root, when help text and metadata links coexist with the card-level hit target.
HTML email remains a separate constraint. Pseudo-classes and external stylesheets are unreliable. Inline color plus text-decoration underline is the durable pattern. Web stylesheets should not assume email parity. The companion piece Link Color in HTML Email covers inbox-specific rules.
Before shipping link styles, confirm body links have a non-color cue unless user testing proves otherwise for your audience. Confirm contrast meets WCAG AA against actual backgrounds including visited state if styled. Confirm focus indicators are visible for keyboard navigation. Confirm hover changes reinforce rather than create affordance. Confirm nav and prose rules are scoped separately. Confirm forced-colors and dark mode were checked manually.
Common mistakes recur across redesigns. Removing underlines globally with one anchor rule. Using outline none without focus-visible replacement. Matching link color to body text with only font-weight five hundred. Relying on opacity for muted links, which reduces contrast non-uniformly against varied backgrounds. Ignoring visited state in token tables because marketing mockups never show it. Shipping link colors from Figma variables that never mapped to production tokens, so engineers hard-coded a close hex once and the design system never owned the role.
Content management systems amplify scoped-reset mistakes. When authors paste rich text from Google Docs, underlines may arrive as inline styles that fight your stylesheet, or disappear when sanitizers strip decoration as presentational noise. Document expected authoring patterns. Prefer markdown or structured blocks where links carry a class the template styles. If rich text is unavoidable, sanitize toward your prose pattern rather than toward bare anchors that inherit global resets.
Regression testing for links should include at least one long-form article on each surface variant the CMS allows, one keyboard tab path through inline references, and one forced-colors pass. Automated contrast checks on link tokens against default background are necessary and insufficient. They do not detect use-of-color failures when hue difference is real in math but invisible in perception. They do not detect hover-only affordance. Pair tooling with a human skim at production font size on a non-retina display if you want the ticket volume the typography refresh ignored.
Custom link color expresses brand. Affordance keeps the product usable. Underlines are not mandatory in every context, but something equivalent is, especially anywhere paragraphs read like paragraphs. Style the hue you want. Keep the cue readers need. The typography refresh that hid help links did not fail because designers lacked taste. It failed because one unscoped reset confused branding consistency with interaction design, and readers paid the cost in tickets until prose links were allowed to look like prose links again.