Skip Link
AccessibilityBrick 0.1.2

Skip Link

Skip Link provides a finished, visible-on-focus bypass link and its matching primary-content destination. Atom owns native anchor behavior, focus transfer, scrolling, target focusability, composition, and refs; Brick owns the overlay presentation and stable customization hooks.

Live example

Built from the published package

Interactive
Skip to preview contentPress Tab to reveal the skip link.
Preview content target
Choose with confidence

Know when Skip Link is the right part

Use it when

Use Skip Link near the start of pages that repeat a header, navigation, sidebar, search area, or other block before primary content. Place SkipLink.Root before that repeated content and pair it with one matching SkipLink.Target around the intended main region.

The application—not AppBar, Sidebar, or Brick—owns page placement. Root should be the first useful focusable control. One pair is sufficient for most pages.

Choose another path when

Do not use Skip Link for an ordinary inline destination, table of contents, pagination, route navigation, or disclosure. Do not add a duplicate pair when the page already has an equally usable bypass mechanism. Use Link for ordinary links and Visually Hidden for text that should never become visually present.

Installation and imports

Import the global Brick stylesheet once:

ts
import "@flowstack-ui/brick/styles.css";

The complete stylesheet above is the recommended default. For a measured route-aware build, replace it with the shared foundation and this component's stylesheet:

tsx
import "@flowstack-ui/brick/styles/core.css"; // once at the application root
import "@flowstack-ui/brick/styles/skip-link.css";

Add the modular stylesheet for every other Brick component the route renders. Do not combine modular styles with styles.css or tokens.css.

Import from the package root or focused subpath:

tsx
import { SkipLink } from "@flowstack-ui/brick";
import { SkipLink } from "@flowstack-ui/brick/skip-link";

The focused subpath also exports SkipLinkRoot, SkipLinkTarget, SkipLinkRootProps, and SkipLinkTargetProps for direct named composition.

Quick start

tsx
import { SkipLink } from "@flowstack-ui/brick/skip-link";

export function ApplicationShell() {
  return (
    <>
      <SkipLink.Root>Skip to main content</SkipLink.Root>
      <header>{/* repeated navigation */}</header>
      <SkipLink.Target>
        <h1>Workspace</h1>
        {/* primary page content */}
      </SkipLink.Target>
    </>
  );
}

The defaults pair href="#main-content" with id="main-content".

Visual recipes and states

Skip Link intentionally has one visual recipe and no size, tone, shape, position, or motion props.

  • Unfocused Root is fixed and translated beyond the viewport while remaining keyboard focusable.
  • Focused Root appears immediately on an elevated neutral surface with accent border, readable body typography, shadow, and strong focus ring.
  • Hover and active paint apply only while the link remains focused.
  • Target receives Brick identity but no component layout or paint.
  • Light and dark appearance scopes substitute semantic tokens.
  • Forced colors use system text, surface, link, and highlight colors.

Examples

Custom destination

tsx
<SkipLink.Root href="#release-review">Skip release navigation</SkipLink.Root>
<nav aria-label="Release">{/* repeated destinations */}</nav>
<SkipLink.Target id="release-review">
  <h1>Release review</h1>
</SkipLink.Target>

Native-only fragment navigation

tsx
<SkipLink.Root href="#content" focusTarget={false}>
  Skip navigation
</SkipLink.Root>
<SkipLink.Target id="content">Primary content</SkipLink.Target>
Public contract

API

Start with the public parts and root options below. Components with multiple parts separate each area into its own named subsection.

SkipLink.Root

SkipLink.Root accepts Atom's native anchor props with these focused additions and defaults:

PropTypeDefaultPurpose
href`#${string}`"#main-content"Matching same-page destination
focusTargetbooleantrueFocus and scroll the existing destination
childrenReactNode"Skip to main content"Localized link name
renderAtom render adapterReplace the default anchor while preserving semantics
asChildbooleanfalseMerge into one authored semantic child

Native anchor props, click handlers, classes, styles, data attributes, custom slots, and an HTMLAnchorElement ref pass through.

SkipLink.Target

PropTypeDefaultPurpose
idstring"main-content"Matching fragment ID
tabIndexnumber-1Programmatic focus without another Tab stop
childrenReactNodePrimary content
renderAtom render adapterReplace the default main
asChildbooleanfalseMerge into one authored landmark child

Native main props, classes, styles, data attributes, custom slots, and an HTMLElement ref pass through.

Shared responsibility

Accessibility

  • Put Root before repeated content and make it the first useful focus target.
  • Write concise localized text that names the destination.
  • Keep Root and Target fragment identifiers unique and matching.
  • Keep the default native anchor and main semantics unless the composed host supplies equivalent semantics.
  • Do not hide Root with display:none, visibility:hidden, Visually Hidden, or a negative tabIndex.
  • Tab focuses Root; Enter activates it. Atom moves focus and scrolls to Target.
  • The next Tab continues from the primary content.
  • Preventing the click also prevents Atom's focus/scroll behavior.
  • focusTarget={false} opts into native fragment navigation only.
  • Brick adds no custom ARIA role or keyboard model.

Skip Link is one sufficient way to meet WCAG 2.4.1 Bypass Blocks; applications remain responsible for their complete page conformance.

Responsive behavior

The revealed link uses logical insets, a bounded viewport-relative maximum width, wrapping text, and no transition. Long localized labels remain inside narrow viewports and the position mirrors in RTL. Applications should test the real sticky header, browser chrome, safe-area policy, and supported 400% zoom layout; Brick does not guess a page-specific scroll offset for Target.

Stable visual contract

Styling and tokens

Customization

Change paint through documented variables while keeping the native focus and target contract:

tsx
<SkipLink.Root
  style={{
    "--brick-skip-link-background": "#2e1065",
    "--brick-skip-link-foreground": "#ffffff",
    "--brick-skip-link-border-color": "#c4b5fd",
    "--brick-skip-link-radius": "999px",
  } as React.CSSProperties}
>
  Skip workspace navigation
</SkipLink.Root>

Consumer classes and inline styles follow normal cascade rules. If a product requires an always-visible bypass link, use the stable Root class in that product's shell policy; Brick does not add a second behavior or public variant.

Tokens and CSS hooks

Stable classes:

  • .brick-skip-link
  • .brick-skip-link__target

Both parts expose a stable data-slot: skip-link on Root and skip-link-target on Target. An authored data-slot overrides the default.

Public Root variables:

VariablePurpose
--brick-skip-link-backgroundRevealed surface
--brick-skip-link-foregroundLink text
--brick-skip-link-border-colorRevealed boundary
--brick-skip-link-border-widthBoundary width
--brick-skip-link-radiusBoundary radius
--brick-skip-link-shadowElevated shadow
--brick-skip-link-focus-ringFocus outline
--brick-skip-link-inset-blockViewport block-start inset
--brick-skip-link-inset-inlineLogical viewport inline-start inset
--brick-skip-link-max-inline-sizeLong-label width bound
--brick-skip-link-z-indexOverlay layer

Advanced reference

Open these details only when you need to inspect DOM ownership, native forwarding, or lower-level composition.

Maintainer resources

Tests, playground evidence, source notes, and release history remain available without crowding the plug-and-play guide.