Skip to content

link

link declares that an element navigates to another location when activated. Without it the element cannot be tabbed to, Enter does nothing, and there is no href for a consumer to point anywhere.

Status — React: Implemented • Web Components: Implemented • iOS: Not yet planned • Android: Not yet planned

An anchor rather than a button, and the distinction is not cosmetic: a link is announced as a link, opens in a new tab on modifier-click, appears in a screen reader’s links list, and is dragged and copied as a URL. A button does none of that.

Roles

Apply the following roles to elements:

RoleTypeElement
linkControlElement of type: container, type: text, or type: glyph
labelPartElement of type: text or nested instance prop of type: string

A glyph carrying the role is re-hosted inside the anchor rather than becoming it, as with button.

States

The link role is typically applied in conjunction with the following states:

StateEffectClassify?
disabledDrops href and announces aria-disabledRecommended
currentaria-current="true"Recommended
hover / activeNative anchor statesRecommended, if the library styles it
focus / focus-visibleNative focus indicatorOptional — prefer the platform default

Two decisions earlier emission made silently:

  • An anchor has no disabled property. Dropping href is what actually removes it from the tab order; aria-disabled alone announces a state the element does not enforce. The css transformer’s disabled selector special-cases anchors to match.
  • current emits the generic token, aria-current="true", matching the states table’s canonical selector. An earlier emission wrote aria-current="page", which no stylesheet selector matched.

Read more about states in specs.

Specs

Component anatomy typically has elements and roles like:

anatomy:
root:
type: container
role: link
text:
type: text

Figma

Annotate the following layers:

  • root as role:link — on the component node, or on an inline text element
  • label as role:label where the link’s name is not its own text

The design file cannot say where a link goes, so href has no source in the spec. It is the one prop this role exists to create a home for.

React

Implementation

<InlineLink href="/pricing">See pricing</InlineLink>
<InlineLink href="/settings" current>Settings</InlineLink>

Before / After

// before
<div className="inline-link" data-element="root">
{p.text}
</div>
// after
<a
className="inline-link"
data-element="root"
href={p.disabled ? undefined : p.href}
aria-disabled={p.disabled ? true : undefined}
aria-current={p.current ? 'true' : undefined}
target={p.target}
rel={p.rel}
onClick={(e) => p.onClick?.(e)}
>
{p.text}
</a>

Contract

PropTypeTierGenerated body
href?stringMUSTDestination. Absent renders a placeholder the browser cannot focus
target?stringSHOULDPassed through
rel?stringSHOULDPassed through
onClick?(e: MouseEvent) => voidSHOULDStub — navigation is the browser’s

Web Components

As button — a real <a> inside the shadow root, all: unset, with delegatesFocus so the host stays one focusable box.

One asymmetry worth knowing: an href is resolved against the document, so href="#section" from inside a shadow root navigates to a light-DOM target normally. The reverse does not hold — an id inside a shadow root is not addressable as a fragment target from outside, so a link elsewhere on the page cannot point into this component.

iOS

Not yet planned. Intended binding:

TypeLink
AnnouncedAs a link; double-tap opens it
disabledNo native equivalent — degrades to plain text

Android

Not yet planned. Intended binding:

TypeClickable text with link semantics
AnnouncedAs a link; double-tap opens it
disabledClickable removed; text remains

Additional details

Accessible name

SourceResult
The link’s own textNames it
The accessibility.label convention’s propEmitted as aria-label
Nothing resolvesWarns

See also

  • button — activation without navigation
  • disclosure — a trigger that expands a region rather than leaving the page
  • Roles overview — the vocabulary and how roles are authored