v0.5

Text Link

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

pnpm dlx @create-ui/cli add text-link

Usage

import { TextLink } from "@/components/ui/text-link"
<TextLink href="/docs">Read the docs</TextLink>

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.

Create UI ships components as source files you copy into your project. Start with the installation guide to scaffold a new app, then browse the component reference to see every primitive in one place.

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.

KeyDescription
TabMoves focus to the link.
EnterActivates the link (anchor follows the href; button fires onClick).
SpaceActivates the link when rendered as as="button".

ARIA notes:

  • The component does not set an explicit role; the implicit <a> or <button> role is what assistive tech announces.
  • When disabled is true and the component renders as <a>, it sets aria-disabled and removes pointer events; the link is still in the tab order, so consider also removing href if the destination is meaningless while disabled.
  • When disabled is true and as="button", the native disabled attribute 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()):

<TextLink className="font-semibold tracking-wide">Read more</TextLink>

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 when underline is true).
  • 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:

[data-slot="text-link"][data-variant="danger"] {
  /* … */
}
  • 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

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.

Props

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element via Radix Slot. Use with next/link's <Link> or any custom link wrapper.
as"a" | "button""a"Switches the host element. Ignored when asChild is true.
leadingIconReact.ReactNode-Rendered before the label inside a data-slot="text-link-icon" wrapper.
trailingIconReact.ReactNode-Rendered after the label inside a data-slot="text-link-icon" wrapper.
underlinebooleanfalseRenders the hairline underline beneath the label.
visitedbooleanfalseForces the visited (violet) style; useful for controlled "already read" states.
disabledbooleanfalseDisables interaction. On as="button" sets the native disabled; on anchors sets aria-disabled and removes events.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The link label.

Variants

VariantOptionsDefaultDescription
variant"primary" "neutral" "inverse" "danger" "success" "info""primary"Color treatment. inverse is tuned for dark surfaces; status tones (danger, success, info) carry semantic weight.
size"xs" "sm" "md" "lg""md"Picks the matching text-paragraph-* scale and icon size. Match the surrounding type scale, not the link's importance.