Skip to content

link

The link role declares that an element navigates to another location when activated.

Why it matters

An anchor, not 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. Without the role, a link scaffolds as a generic container: it cannot be tabbed to, Enter does nothing, and it has no href for a consumer to point anywhere.

Emission

Scaffold

Element<a href>
Accepted element typescontainer, text, glyph
Accepted partslabel

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

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; what a click means beyond it is the consumer’s

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

States

StateWhat the link doesClassify in states?
disabledDrops href — which is what actually removes it from the tab order — and announces aria-disabledRecommended
currentaria-current="true"Recommended
hover / activeNative anchor statesRecommended, if the library styles it
focus / focus-visibleNative focus ringOptional — prefer the platform default

Two decisions recorded here that earlier emission made silently:

  • Disabled. An anchor has no disabled property. A disabled link drops href and carries aria-disabled="true"; aria-disabled alone would announce a state the element does not enforce. The css transformer’s disabled selector special-cases anchors to match.
  • current is a role concern, bridged natively like pressed on a togglebutton: the role emits aria-current="true" — the generic token, matching the states table’s canonical [aria-current="true"] selector — and the states convention supplies the prop. An earlier emission wrote aria-current="page", which no stylesheet selector matched; the generic token is the recorded resolution.

Accessible name

A link takes its name from its own text. Where none resolves, the accessibility.label convention’s prop emits as aria-label, and nothing resolving is a warning — a link that announces nothing is worse than the container it replaced.

Platforms

EmitsBehavior a user gets
Web<a href>Tab-focusable, Enter navigates, modifier-click opens a tab, appears in the links list
iOSLinkVoiceOver announces it as a link and double-tap opens it
AndroidClickable text with link semanticsTalkBack announces it as a link and double-tap opens it

Before and after

Without the role:

<div className="ds-inline-link" data-element="root">
{p.text}
</div>

With the role:

<a
className="ds-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>

See also

  • button — activation without navigation
  • disclosure — a trigger that expands a region rather than leaving the page
  • Roles overview — how roles and the states convention fit together