Link Button

How the Link Button works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

FreeReact and Vue
AAWCAG 2.2 target
12/18contrast pairs passing
4keyboard interactions

The Link Button renders a real <a> when it has an href and a native <button> when it does not, so the browser supplies the role and keyboard behaviour. The choice matters: a link goes somewhere, a button does something. What is left to the component is colour that reads as text, and a focus ring that shows.

Keyboard#

Tab

Moves focus to the link button. A disabled <button> is skipped.

Shift+Tab

Moves focus to the previous focusable element.

Enter

Follows the link, or activates the button.

Space

Activates the button form only. A link does not respond to Space, which is expected.

Semantics#

<a href>

Navigation. Announced as a link and listed in the screen reader's links list.

href set
<button type="button">

An in-page action. Announced as a button.

No href
aria-disabled="true"

The href is removed, so the link is no longer followed or focusable. Say why it is unavailable nearby.

disabled with href
disabled

Removed from the tab order and not announced as actionable.

disabled without href
aria-hidden

The 28px chevrons are decorative and hidden from assistive tech.

Chevrons

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.

4.5:1

Label text · Rest, Hover, Pressed

The 16px bold label against the surface behind it, which is the page itself at rest. Hover with Default Underline? on adds a 6% wash, so measure the label over that.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1 or underline

Telling a link from text · Inline in a sentence

Inside running text, colour alone has to differ from the surrounding text by 3:1, or the link needs a non-colour cue. Turn on Default Underline? when a link button sits in a sentence.

WCAG 1.4.1 Use of Color ↗
3:1

Focus indicator · Focus visible

A 2px #1a6ef8 stroke inside the box: 4.54:1 on white, 3.16:1 on the #2a2a2a Inverse surface. It shows for keyboard focus only.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗
Not required

Disabled · Disabled

Inactive controls are exempt. The disabled label (#cbcbcb) is deliberately faint and loses its underline.

WCAG 1.4.3 Contrast (Minimum) ↗

Measured contrast#

Label against the surface behind it, for every intent and state. Hover is measured with Default Underline? on, where it adds a wash. Inverse on dark is measured on #2a2a2a, everything else on white. 6 of 18 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

IntentRestHoverPressed
Neutral · Link5.17passes12.60passes20.14passes
Brand · Link5.40passes4.81passes13.54passes
Success · Link3.68below the minimum3.56below the minimum7.62passes
Warning · Link4.00below the minimum3.86below the minimum15.82passes
Error · Link4.49below the minimum4.17below the minimum11.60passes
Inverse on dark · Link14.35passes9.74passes8.85passes

Motion#

  • Colour and wash changes take 150ms and are turned off under prefers-reduced-motion.
  • The underline appears at once; it does not animate.

Checklist#

Pass an href whenever the action navigates, so it renders as <a> and opens in a new tab like any link.

The label makes sense out of context: View invoice, not Click here or Learn more on its own.

An external destination says so in the label or an aria-label, not only with an icon.

Success (#229a14), Warning (#be6a00) and Error (#ec0d33) sit below 4.5:1 at rest and on hover: see the table. Use Neutral or Brand for link text until those shades are darkened.

At 36px tall the link button clears the 24px target minimum; keep that height when it sits inline rather than shrinking it to the line.