flyingsalmon

Text Link

An anchor for a sentence or a line of UI copy. It takes its size and weight from the text around it and carries an underline that never leaves; a link navigates and a button acts.

If it acts, it is a button

A link navigates: to another page, another site, or a place on the same page. A button acts: it sends, saves, opens, or retries. TextLink always renders an anchor with an href, or your router's link through asChild. There is no href-less text link: an anchor that navigates nowhere is an action, and an action is a button. An action inside a sentence — "Didn't get the code? Resend" — is the primary button placed inline, and the registry ships no text-looking button variant.

Navigation items are not text links either. Breadcrumb's levels, sidebar's items, menu items, and pagination's page numbers read as links by their position, so they keep their own classes and draw no underline. Card and table stretched links stay as they are: the surface is the hit target, and an underline inside it would compete with the title.

Inside a paragraph

The link is display: inline, so it breaks across lines with the sentence it sits in and never forces a line of its own. Font size, weight, and line height are inherited — there is no size prop and no variant. The text is --foreground in every state; the underline sits at underline-offset-4 and is 1px --muted-foreground at rest, 1.5px --foreground on hover and press. It thickens downward from the offset, so the line box never moves. Tab to the link below and the focus ring closes around each line fragment separately, which is what box-decoration-clone is for.

Light

Every itinerary starts from a template, and the one we reach for most is the three days in Kyoto walking route that begins at Fushimi Inari, which fits temples, tea, and a river walk into a weekend.

Dark

Every itinerary starts from a template, and the one we reach for most is the three days in Kyoto walking route that begins at Fushimi Inari, which fits temples, tea, and a river walk into a weekend.

Inside muted text

In an alert description, a field description, or any muted block, the link keeps --foreground while its surroundings stay --muted-foreground, so it reads one step stronger than the copy around it. That is why there is no muted variant and no currentColor inheritance: the contrast with the surrounding text is the point.

Light
Two travellers have no passport on file
Add their documents on the travellers page before you book.
Dark
Two travellers have no passport on file
Add their documents on the travellers page before you book.

External links

There is no external prop. Whether a link opens a new tab is your app's policy, so you pass target and rel yourself, and you place the icon as a child. An svg child sits inline at text scale, so it grows and shrinks with the sentence instead of holding a fixed pixel size.

Light

Japan's rail passes are explained on japan.travel, which lists every regional option.

Dark

Japan's rail passes are explained on japan.travel, which lists every regional option.

Router links

asChild hands the class names and the props to the element you pass, so your router renders the anchor and client-side navigation keeps working. This is the same slotting BreadcrumbLink and DropdownMenuItem use. The component is named TextLink rather than Link precisely because this case nests the two.

Light

Everything on this page is built from the registry components.

Dark

Everything on this page is built from the registry components.

On another surface

The focus ring stands 2px off the text, and that gap paints --background. On a card, or any surface that is not the page, pass focus-visible:ring-offset-card through className so the gap matches what the link sits on — the same override alert's close button makes.

Light
Weekend in Kyoto
Three days, ten stops, and one very long river walk at dusk.
Dark
Weekend in Kyoto
Three days, ten stops, and one very long river walk at dusk.

States, motion, and accessibility

Rest, hover, press, and focus are the whole set. There is no visited state — browsers restrict :visited to colour, and "visited" means nothing for a router link — and no disabled state, because an anchor that cannot navigate is plain text. The only thing that moves is the underline: its colour and its thickness together, at --motion-fast. Thickness carries the change where colour alone reads faint — a 1px dark stroke on a light ground loses to antialiasing in a way a light stroke on a dark ground does not. The underline never draws in, because it is never absent.

Tab reaches the link and Enter activates it, the native anchor behaviour. Text runs --foreground on --background, the body-text pair; the resting underline is --muted-foreground at 4.73:1 light and 7.63:1 dark, and the hover underline is --foreground at 1.5px. WCAG 1.4.1 does not apply here: the underline never leaves, so the link is never told apart by colour alone.