Inline anchor for in-body links that stay inside the flow of text.
Description
TextLink is an inline link element. It renders as a native <a> by default, switches to a <button> with as="button", or wraps any child element via asChild (Radix Slot), so it slots into framework routers like next/link without losing styling or accessibility. Leading and trailing icons, an optional underline, and visited / disabled states are first-class props.
Reach for it inside body copy, list items, table cells, form helpers, card descriptions, and any other place a link needs to read as text rather than as a button. The default sits flat in the line until hover; turn on underline for standalone "Learn more" links, in-row CTAs, and any context where the link needs to be discoverable without surrounding prose.
Don't use TextLink when the control needs a filled hit area; reach for Button (optionally asChild wrapping <a>). For menu rows use DropdownMenu.Item, for breadcrumb trails use Breadcrumb.Link, and for selectable or removable tag-style affordances use Chip.
Installation
Usage
Examples
Variants
variant picks the color treatment. primary (default) is the standard product link; neutral blends into body copy for tertiary references; status tones (danger, success, info) carry semantic weight; inverse is tuned for dark surfaces.
Underline
underline (default false) lets the link sit silently inside running text; hover still reveals the underline animation. Turn it on for standalone "Learn more" links, in-row CTAs, and any place the link must read as a link without the surrounding sentence.
Sizes
Four sizes (xs, sm, md (default), lg) match the surrounding text-paragraph-* scale and resize the icon slot accordingly. Pick the size that matches the body copy you're embedding into, not the link's importance.
With Icon
Pass leadingIcon and / or trailingIcon for external-link, arrow, or status glyphs. Icons are wrapped in data-slot="text-link-icon" spans and inherit the link's color and size automatically.
States
visited forces the violet "already read" style, which is useful for controlled lists like recent docs. disabled mutes the link and removes pointer events; on as="button" it also sets the native disabled, on anchors it sets aria-disabled.
In Prose
The primary use case: a link mid-sentence inside body copy. Keep underline off when the surrounding sentence already telegraphs that the phrase is interactive; turn it on for the rarer case where the link must stand out even when skim-reading.
Render As
TextLink is polymorphic. By default it renders an <a> (give it an href). Pass as="button" for in-page actions that should not be anchors. Pass asChild and wrap a framework <Link> (next/link, react-router, @tanstack/router, and so on) to keep client-side routing intact while inheriting all of TextLink's styling.
Accessibility
TextLink renders a native <a> by default, so the browser's link semantics, focus order, and middle-click or cmd-click behavior come for free. Switch to <button> with as="button" whenever the control triggers an in-page action rather than navigation; using a button keeps the right keyboard contract and screen-reader announcement.
ARIA notes:
- The component does not set an explicit
role; the implicit<a>or<button>role is what assistive tech announces. - When
disabledistrueand the component renders as<a>, it setsaria-disabledand removes pointer events; the link is still in the tab order, so consider also removinghrefif the destination is meaningless while disabled. - When
disabledistrueandas="button", the nativedisabledattribute is applied (so the button is removed from the tab order automatically). - Leading and trailing icons are wrapped in spans with no role and are treated as decorative; the accessible name must come from the link text. If you must render an icon-only link, pass an explicit
aria-label. - When using
asChild, accessibility responsibility shifts to the rendered child; make sure the wrapped element has an accessible name and the right role.
Styling
Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):
Data slots and attributes: the component sets these for CSS targeting:
data-slot="text-link"on the root element.data-slot="text-link-content"on the inner label wrapper.data-slot="text-link-underline"on the underline strip (only whenunderlineistrue).data-slot="text-link-icon"on each icon wrapper (leading and trailing).data-variant="<variant>",data-size="<size>"on the root.
Target a specific state in CSS:
Related Components
- Button (
asChild): use when the link needs a filled or outlined hit area, not inline text styling. - Breadcrumb.Link: sibling links inside a breadcrumb trail with separator handling built in.
- DropdownMenu.Item: link-like rows inside a menu, with hover and keyboard navigation handled.
- Chip: interactive label with a different visual weight (selectable, removable).
API Reference
TextLink
Inline anchor for in-body links. Renders as an <a> by default; render as a <button> with as="button" or as any element with asChild via Radix Slot. Extends React.ComponentProps<"a"> (minus color) plus type from React.ComponentProps<"button">, so any standard anchor or button attribute (href, target, rel, onClick, aria-*, etc.) is accepted.