Components · Navigation

Links

Interactive text. Three variants — inline (underlined), standalone (no underline at rest), subtle (body-coloured) — plus an external-link affordance and a disabled state. The token is blurple-800; the rule is that the underline tells the eye where the affordance is.

Documentedby Derek Fidler

Read the colour foundations before reaching for a custom hex.

Inline — within prose

Overview

Links take the user somewhere. The component renders a real <a> with the right ARIA + rel attributes, and lets the rest of the page lean on one of three visual patterns depending on where it sits — inside prose, next to a heading, or buried in a meta strip.

Link, not button

Use a Button when the action stays on the current page (submit a form, open a dialog, copy a value). Use a Link when the user navigates — to another page, an external site, or a section of the current page via an anchor. The back button should always work after a Link.

Variants

Three patterns from .impeccable.md. The right pattern is a function of where the link sits on the page; getting it wrong is what makes a layout feel busy or unfindable.

Read the colour foundations before reaching for a custom hex.

variant="inline"

The default. Used inside prose where the underline is the affordance — a paragraph of body copy with one or two clickable phrases.

variant="standalone"

Used next to section headings ("See all"), in lists, and anywhere position carries the affordance — no underline at rest, underline on hover.

Last synced 3h ago by @derekfidler.

variant="subtle"

Looks like body text; only the hover underline reveals the link. Used in dense rows where the link sits among non-link text.

External & disabled

External destinations get a small glyph and the safe rel attributes; disabled links sit dim and don't fire on click. Both states are auto-detected when possible.

The brand fonts come from Klim Type Foundry and Rasmus Andersson.

External link

Auto-detected when href starts with http(s). Adds rel="noopener noreferrer", target="_blank", and a small external-link glyph.

The audit trail will be available here once governance ships.

Disabled

Strips the href, sets aria-disabled, drops the underline. The user can tab to it but pressing Enter does nothing.

Patterns

Four real surfaces — prose paragraph, section header, vertical list, dense metadata strip. The variant changes; the affordance stays consistent.

Currency follows the European format — period thousands, comma decimal. Negatives use orange, not red, because outgoing money is normal product motion.

In prose

Inline links sit naturally in body copy. The underline tells the eye where the affordance is without scanning.

Recent prototypes

See all →

Section header

A standalone "See all" link at the top-right of a section heading. No underline at rest so it reads as a quiet sibling, not a competitor.

List of links

Standalone links stacked in a column. The position carries the affordance; underline appears on hover for confirmation.

Owner @derekfidler · Repo derekfidler/lucky-garden

Model claude-opus-4-7 · Last synced 3h ago

Metadata strip

Subtle links inside a meta line — the link reveals on hover so the row reads as one unit at rest.

Anatomy

Three named parts. Only the label is required — the underline is variant-driven, the external glyph appears automatically.

  1. Label

    Inter Tight Medium. The text the user reads. Coloured #4B63C1 (text.link, blurple-800) for inline + standalone variants; foreground for subtle.

  2. Underline

    Always visible on inline; only on hover for standalone and subtle. Thickens to 2 px on hover.

  3. External glyph

    6 px diagonal-arrow icon. Appears automatically for http(s):// hrefs and whenever external is true.

Behavior

  • External auto-detected. Hrefs starting with http:// or https:// get target="_blank" and rel="noopener noreferrer", plus the external glyph. Pass external={false} to opt out (e.g. for absolute internal URLs in dev).
  • Disabled is honest. disabled strips the href, sets aria-disabled, and preventDefaults click. The link stays focusable so screen-reader users hear “link, disabled”.
  • Underline thickens, not colour. Hover swaps decoration weight from 1 px to 2 px and shifts the colour from #4B63C1 to #32448F. Subtle, not loud — links never bounce or change size.
  • Composes with router primitives. Renders a plain <a>. For Next.js client routing, wrap with next/link; the ref forwards through.

Accessibility

  • Native semantics. Renders an <a>; no custom ARIA role. Screen readers announce as “link”.
  • External tabs are safe. rel="noopener noreferrer" is auto-applied so the new tab can't hijack window.opener or leak the referrer.
  • Underline carries the affordance. Inline links are always underlined — colour alone is not enough for users with reduced colour perception. Standalone and subtle variants rely on the position and the hover reveal.
  • Focus ring is the system ring. focus-visible shows the 2 px #9EB7FFring with a 2 px offset — keyboard users always see where they are. Don't override.
  • Don't hyperlink full phrases. Per the localisation rules — concatenated translations break, and assistive tech reads the whole phrase as one link. Underline a verb plus a noun, not the whole sentence.

Code

tsx

import { Link } from "@flatpay-dk/ui";

// Inline — default. For prose with one or two clickable phrases.
<p>
  Read the <Link href="/foundations/color">colour foundations</Link>{" "}
  before reaching for a custom hex.
</p>

// Standalone — for "See all", side rail, list of links.
<Link href="/components" variant="standalone">
  See all components →
</Link>

// Subtle — body-coloured, for dense meta strips.
<Link href="/u/derekfidler" variant="subtle">@derekfidler</Link>

// External — auto-detected, gets target="_blank" + rel + glyph.
<Link href="https://flatpay.com">flatpay.com</Link>

// External, opt-out.
<Link href="https://internal.flatpay.dev" external={false}>
  Internal, same tab
</Link>

// Disabled.
<Link href="/audit" disabled>here</Link>

// With Next.js client routing.
import NextLink from "next/link";

<NextLink href="/foo" passHref legacyBehavior>
  <Link>Foo</Link>
</NextLink>

Best practices

Read the colour foundations before reaching for a custom hex.

Do

Underline a verb + noun. The eye lands on what's interactive without scanning.

Don't

Don't hyperlink the whole sentence. Long links read as a wall and break under translation.

Recent prototypes

See all →

Do

Use standalone for 'See all' next to a heading. No underline at rest keeps the heading the heading.

Don't

Don't use a button for navigation. Buttons act on the current page; links go somewhere new.

Props

PropTypeDefaultDescription
hrefstringDestination URL. Anchor (#fragment), absolute path (/path), or full URL (https://).
variant"inline" | "standalone" | "subtle""inline"Visual pattern. inline = underlined; standalone = underline on hover; subtle = body-coloured, underline on hover.
externalbooleanOverride external auto-detection. true forces target="_blank" + rel; false suppresses both even on http(s) hrefs.
disabledbooleanfalseDrops the href, sets aria-disabled, and preventDefault()s click. Stays focusable so screen readers announce 'link, disabled'.
...restAnchorHTMLAttributes<HTMLAnchorElement>Every standard anchor prop passes through (target, rel, onClick, download, hreflang).