Skip to content

Skip-link component

components specs/components/skip-link.kmd

The keyboard-only shortcut that lets a user jump straight past repeated chrome (topbar, nav) to the page's real content. Visually absent until it takes keyboard focus, then it slides into view. Multiple targets (content, navigation, search) may be offered, each with an optional shortcut-key hint. Required by WCAG 2.4.1 (Bypass Blocks) and eMAG 3.1; the gov.br DS ships it as `.br-skiplink`, and KDS had focus-ring/keyboard support but no component.

When this spec applies

Primary triggers

All triggers

Specification body

Component — Skip link

Status: v0.1.0 — Draft. Opened from the KDS × gov.br gap analysis (tools/design-gen/docs/govbr-ds-gap-analysis.md, stack#395): gov.br ships skiplink (eMAG); KDS had focus-ring + keyboard nav but no skip-link component. Behaviour below is MEASURED from @govbr-ds/core dist/core.min.css v3.7 (.br-skiplink, 8 occurrences), not inferred. Normative basis: WCAG 2.4.1 (Bypass Blocks, level A) + eMAG 3.1 (Recomendação 1.5 — "fornecer âncora para ir direto a um bloco de conteúdo").

Why it exists

A keyboard or screen-reader user meets the same topbar and navigation on every page. Without a bypass they tab through all of it, every time, before reaching what they came for. The skip link is the standard escape hatch — and it is the one component whose whole value is that sighted mouse users never see it.

R1 — Hidden off-screen, NEVER display: none

The link is present in the DOM and in the tab order at all times, and hidden by moving it out of the viewport — gov.br uses top: -100vh on a position: absolute anchor inside a position: fixed container.

display: none / visibility: hidden / hidden are FORBIDDEN here, and this is the rule the whole component hangs on: those remove the element from the tab order, so it can never receive the focus that is supposed to reveal it. A skip link you cannot tab to is not a degraded skip link — it is a decoration that silently fails the criterion it exists to satisfy.

Equally forbidden: rendering it only on :hover, or gating it behind a media query — a keyboard user has no pointer, and the need is not viewport-sized.

R2 — Focus reveals it

On :focus and :focus-visible the link returns to the viewport (gov.br: top: 0, transition 150ms ease-out). On blur it leaves again.

Both pseudo-classes are asserted on purpose: :focus-visible alone would let a UA that withholds the heuristic hide the link from a real keyboard user, and :focus alone re-shows it on a programmatic focus that no human drove.

R3 — Multiple targets

The container is a column (gov.br: flex-direction: column), so a surface MAY offer several bypasses — typically content, navigation, search — in that order (most-wanted first). Each is an anchor to a real landmark id on the page; a skip link pointing at an id that does not exist is a broken promise and MUST fail the surface's a11y gate rather than silently no-op.

R4 — Shortcut hint

Each link MAY carry a shortcut-key hint rendered as a tag (gov.br nests .br-tag with background: var(--interactive); the KDS counterpart is chips.kmd). The hint is decorative: the link MUST work by plain tab-and-enter with no shortcut bound.

R5 — Geometry + placement (per active DS)

position: fixed, pinned to the top-left of the viewport, above all chrome. gov.br's measured values: left: --spacing-scale-2x (16px), min-width: 300px, z-index: --z-index-layer-4, box-shadow: --surface-shadow-lg, background --background.

Per design-RFC-014, a Koder surface takes these from the active design system, not from hardcoded constants — the same rule the rest of the kit now follows (theme::Shape / theme::Palette, kroma#189/#278). The skip link is chrome, so it sits above the topbar in stacking order (specs/app-layout/topbar-placement.kmd).

R6 — Accessibility

  • The link text is a real, translated sentence ("Pular para o conteúdo" / "Skip to content") — never an icon or an abbreviation (specs/i18n/contract.kmd; the en-US + pt-BR floor applies).
  • It is the first focusable element on the page. A skip link you reach after the topbar has bypassed nothing.
  • Activating it moves focus to the target landmark, not merely the scroll position — a screen reader that scrolls but does not move focus keeps reading the nav.

R7 — Test

A surface claiming this component MUST prove, headlessly:

  1. the link is focusable (tab from page load reaches it first) — this is the negative control for R1: a display:none regression fails here and nowhere else;
  2. focusing it brings it into the viewport (R2), and blurring removes it;
  3. every target id resolves to a landmark on the page (R3);
  4. activation moves focus to that landmark (R6).

Asserting only (2) is the trap: a link that is visible-on-focus but unreachable by tab passes the eye test and fails the user.

  • specs/accessibility/conformance-br.kmd — eMAG 3.1 obligations for BR surfaces.
  • specs/app-layout/topbar-placement.kmd — the chrome it must sit above and bypass.
  • specs/components/chips.kmd — the shortcut-hint chip/tag (R4).
  • design-RFC-014 — the active-DS geometry/colour source (R5).

Requirements (testable)

The skip link SHALL remain present in the DOM and reachable in the tab order at all times, hidden only by being moved out of the viewport; it SHALL NOT be hidden with display: none, visibility: hidden, the hidden attribute, a :hover-only rule, or a media query, because any of those removes it from the tab order it depends on.

  • GIVEN a page that declares the skip-link component
  • WHEN the page loads and the user presses Tab once with no prior focus
  • THEN the skip link receives keyboard focus as the first focusable element
  • AND its computed style is not display: none and not visibility: hidden
  • GIVEN the skip link rendered but not focused
  • WHEN its computed geometry is inspected
  • THEN it is positioned outside the viewport (e.g. top: -100vh) rather than removed from layout
  • AND it still appears in the document's sequential tab order

The skip link SHALL return into the viewport on both :focus and :focus-visible, and SHALL leave the viewport again on blur, so that a keyboard user always sees it when it holds focus and a mouse user never does.

  • GIVEN the skip link off-screen and unfocused
  • WHEN it receives keyboard focus
  • THEN it animates back into the viewport (e.g. top: 0)
  • AND its bounding box is fully within the viewport
  • GIVEN the skip link currently focused and visible in the viewport
  • WHEN focus moves away from it
  • THEN it returns to its off-screen position
  • AND its bounding box is outside the viewport

When a surface offers one or more bypasses (typically content, navigation, search, in that order), each link SHALL point at the id of a real landmark that exists on the page; a link whose target id does not resolve SHALL fail the surface's accessibility gate rather than silently no-op.

Scenario: All target ids resolve

  • GIVEN a skip-link container offering content, navigation, and search bypasses
  • WHEN the a11y gate resolves each link's target id against the DOM
  • THEN every referenced id matches an existing landmark element on the page

Scenario: A dangling target fails the gate

  • GIVEN a skip link whose href points at an id absent from the page
  • WHEN the surface's accessibility gate runs
  • THEN the gate reports a failure for the unresolved target
  • AND the surface is not allowed to pass

Activating a skip link SHALL move keyboard focus to the target landmark, not merely change scroll position, so that a screen reader resumes reading at the bypassed content instead of the chrome.

Scenario: Enter moves focus past the chrome

  • GIVEN the skip link focused as the first element on the page
  • WHEN the user activates it with Enter
  • THEN keyboard focus moves to the referenced target landmark
  • AND the active element is inside that landmark, not the topbar or nav

Each link MAY carry a decorative shortcut-key hint rendered as a tag/chip, but it SHALL operate by plain tab-and-enter with no shortcut key bound; the hint SHALL NOT be required to reach or activate the link, and the link text SHALL be a real translated sentence rather than an icon or abbreviation.

  • GIVEN a skip link that displays a shortcut-key hint chip
  • WHEN the user reaches it by Tab and presses Enter without pressing any shortcut key
  • THEN focus moves to the target landmark
  • AND the link text is a full translated sentence (e.g. "Skip to content" / "Pular para o conteúdo")

References