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.
Label
Inter Tight Medium. The text the user reads. Coloured
#4B63C1(text.link, blurple-800) for inline + standalone variants;foregroundfor subtle.Underline
Always visible on
inline; only on hover forstandaloneandsubtle. Thickens to 2 px on hover.External glyph
6 px diagonal-arrow icon. Appears automatically for
http(s)://hrefs and wheneverexternalistrue.
Behavior
- External auto-detected. Hrefs starting with
http://orhttps://gettarget="_blank"andrel="noopener noreferrer", plus the external glyph. Passexternal={false}to opt out (e.g. for absolute internal URLs in dev). - Disabled is honest.
disabledstrips the href, setsaria-disabled, andpreventDefaults 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
#4B63C1to#32448F. Subtle, not loud — links never bounce or change size. - Composes with router primitives. Renders a plain
<a>. For Next.js client routing, wrap withnext/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 hijackwindow.openeror 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-visibleshows 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
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | — | Destination 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. |
| external | boolean | — | Override external auto-detection. true forces target="_blank" + rel; false suppresses both even on http(s) hrefs. |
| disabled | boolean | false | Drops the href, sets aria-disabled, and preventDefault()s click. Stays focusable so screen readers announce 'link, disabled'. |
| ...rest | AnchorHTMLAttributes<HTMLAnchorElement> | — | Every standard anchor prop passes through (target, rel, onClick, download, hreflang). |