Link button

Link button is a text-only, low-emphasis action for navigation or small in-page actions. Use it for “View all” and “Back” links, or quiet actions like “Resend code”.

  • Beta
  • Accessibility
  • Figma Link Buttons 8:5500
  • packages/ui/src/components/LinkButton

Beta until a designed focus treatment exists (C10) and the Figma inconsistencies in small and underlined variants are fixed (C9).

Preview

Change the controls to try every supported combination. With an href the component renders a link; without one, a button. The code updates with it.

tsx
import { LinkButton } from '@nuton/ui'

<LinkButton href="/library" trailingIcon={<RiArrowRightSLine />}>View all lessons</LinkButton>

Installation

Availability

@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.

1. Add the packages

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react

Peer dependencies: react and react-dom 18 or later. @remixicon/react supplies the icons you pass to link buttons (Remix Icon is Nuton's icon set, D1).

2. Load tokens and component styles once

tsx
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css'     // component styles (uses the tokens)

Link button uses the Label type styles, which read --nu-font-body (Inter). If you already set this up for Button, there's nothing more to do.

Usage

tsx
import { LinkButton } from '@nuton/ui'
import { RiArrowRightSLine } from '@remixicon/react'

// Navigation: renders <a href>
<LinkButton href="/library" trailingIcon={<RiArrowRightSLine />}>View all lessons</LinkButton>

// In-page action: renders <button type="button">
<LinkButton onClick={resendCode}>Resend code</LinkButton>
  • The default is gray, md, no underline: Text/Sub-600 at 14px, darkening to Strong-950 on hover.
  • href decides the element. Pass it to go somewhere; leave it out to do something on this page.
  • Keep labels short and specific: “View all lessons”, “Back to Biology 101”, “Resend code”. Sentence case.
  • Use it for secondary, low-emphasis actions. The main action in a view is a Button.
  • Pass Remix icons through leadingIcon / trailingIcon. They're decorative and hidden from screen readers.

Variants and sizes

Three props shape a link button: tone sets the colour, size sets the type scale and underline marks it as a link in running text. Every tone works with every size and underline setting.

Link or button

The same component renders two different elements. With href it renders <a href>, so people can open it in a new tab, copy it and hear it announced as a link. Without href it renders <button type="button"> for an action on the current page. Both look identical.

3 unread lesson updates.

You passRendersRoleKeysDisabled
href<a href>linkEnterhref removed, role="link" and aria-disabled="true"; leaves the tab order
No href<button>, type="button" by defaultbuttonEnter / SpaceNative disabled; leaves the tab order

Tone × size

ToneUse forTextHoverFigma Style
grayThe default. Secondary links next to other content: “View all”, “Skip for now”.Text/Sub-600Text/Strong-950Gray
blackA link that needs more weight than gray, such as a section header's “View all”.Text/Strong-950Text/Sub-600Black
primaryInline links and calls to action that should read as interactive blue.State/Primary/BasePrimary/700Primary
errorQuiet destructive actions: “Remove”, “Leave space”.State/Error/BaseError/700Error
whiteDark surfaces only, such as a dark banner.Text/WhiteNo changeWhite

Sizes

SizeRow heightTypeIconsIcon gapPadding
md20Label/Small (14/20, medium)20 × 204 (--nu-space-1)None
sm16Label/X Small (12px, medium) on a 16px line16 × 16

Values from the component CSS and tokens, matching Figma's Medium (20) and Small (16). In Figma, the small underlined variants use 14px text; the code follows the majority pattern of 12px (C9). The component has no padding, so the hit area is the text and icons.

Underline

Your notes are saved in this space. Open notes

underline underlines the label only (2px offset), never the icons. Use it whenever the link sits in running text, so it isn't marked by colour alone (WCAG 1.4.1).

Icons

Icons are wrapped in aria-hidden spans, so they never carry meaning. There's no icon-only mode: children is required. For an icon-only action, use Button with iconOnly.

States and interactions

DefaultResting state.
HoverShown here statically. The colour moves to the tone's hover token, only on devices with hover (@media (hover: hover)). White doesn't change.
FocusTab to this link to see it. Focus takes the hover colour and keeps the native focus outline, because Figma defines no focus indicator (C10).
View all
DisabledDisabled-500 text for every tone except white on dark. A disabled link loses its href; a disabled button is natively disabled. Neither can be focused.

State tokens

Each tone sets two component tokens. Override them on a wrapper only for a documented exception.

TokenGrayBlackPrimaryErrorWhite
--nu-link-colorText/Sub-600Text/Strong-950State/Primary/BaseState/Error/BaseText/White
--nu-link-color-hoverText/Strong-950Text/Sub-600Blue/700Red/700Text/White

Disabled uses --nu-text-disabled-500 directly. Focus (:focus-visible) uses --nu-link-color-hover. There's no pressed or visited style; Figma defines neither.

Composition examples

Section header link

A “View all” link next to a list heading. It navigates, so it has an href.

Recent lessonsView all

Form helper action

An in-page action under a field. It has no href, so it renders a button. During the cooldown it stays enabled and explains the wait, because a disabled button can't keep focus.

Ask your teacher for the 6-digit code for Biology 101.

Quiet destructive action

The error tone for a per-row action. Each button's aria-label names its lesson; focus moves to the next row after a removal.

  • Introduction to cellsLesson 1 of 3
  • Photosynthesis basicsLesson 2 of 3
  • Cell division quizLesson 3 of 3

On a dark surface

Lesson published to Biology 101.

Only use white on Bg/Strong-950 or a similarly dark surface. On white it's invisible.

Link button is the current answer for navigation (C27). Button always renders <button> and has no link mode, so when a step navigates, use a link button rather than a button with an onClick.

Gap: no filled link (C27)

There's no way to give a link the filled-button look, for example a “Get started” call to action that navigates. Link button is text-only. Don't wrap a <Button> in an <a> (nested interactive elements). The proposed fix, awaiting approval, is an href or asChild prop on Button.

API reference

Props

Link button props
PropTypeDefaultDescription
childrenrequiredReactNode—The visible label. Also the accessible name.
hrefstring—Renders <a href>. Leave it out to render <button>.
tone'gray' | 'black' | 'primary' | 'error' | 'white''gray'Colour (Figma Style). White is for dark surfaces.
size'sm' | 'md''md'Row height 16 / 20px: Label/X Small or Label/Small.
underlinebooleanfalseUnderlines the label (Figma Underline=On).
leadingIconReactNode—Icon before the label. Rendered aria-hidden.
trailingIconReactNode—Icon after the label. Rendered aria-hidden.
disabledbooleanfalseButton: native disabled. Link: removes href and onClick, and sets role="link" and aria-disabled="true".
type'button' | 'submit' | 'reset''button'Button only (no href). Native type; defaults to button so it never submits by accident.
refRef<HTMLAnchorElement | HTMLButtonElement>—Forwarded to the rendered a or button.
…restAnchorHTMLAttributes | ButtonHTMLAttributes—With href, any native anchor attribute (target, rel, download, aria-*…). Without, any native button attribute (onClick, form, name…). className is merged.

Styling hooks

The component renders .nu-link-button with data-tone, data-size and data-underline, plus .nu-link-button__label and .nu-link-button__icon inside. Use props rather than styling these. The component tokens --nu-link-color and --nu-link-color-hover (see State tokens) are the supported override points.

Accessibility

Keyboard

KeyBehaviour
TabMoves focus to the link button. Disabled link buttons are skipped.
EnterWith href: follows the link. Without: activates the button.
SpaceWithout href only: activates the button. On a link, Space scrolls the page (native).

Screen readers

Role link with href, button without. The name is the label text; icons are hidden. A disabled link keeps role link and is announced as unavailable; a disabled button as dimmed.

Focus

Focus takes the tone's hover colour and keeps the browser's native outline (on this site, the docs' 2px Primary ring). Figma defines no focus indicator, and none for white (C10).

Contrast and colour

On white: gray 6.69:1, black 17.93:1, primary 5.59:1, all passing AA. Error is Red 700, 5.26:1 (C6; Figma's State/Error/Base is 4.15:1). White on Bg/Strong-950 is 17.93:1. Disabled text is 1.53:1 (C8, D7). Use underline in running text so links aren't marked by colour alone.

Touch and pointer

There's no padding: the target is 20px (md) or 16px (sm) tall, below the 24 × 24 minimum. It passes WCAG 2.5.8 only inline in a sentence or with enough space around it. Hover styles apply only on hover-capable pointers.

You must: pass href for navigation and leave it out for actions; give repeated labels a unique aria-label (“Remove Photosynthesis basics”); say in the label when a link opens a new tab; keep at least 24px between link buttons outside running text.

Figma mapping

Figma propertyFigma valuesCode
StyleGray · Black · Primary · Error · Whitetone (default gray)
SizeMedium (20) · Small (16)size: md · sm. Small underlined uses 14px in Figma; code uses 12px (C9)
UnderlineOff · Onunderline
StateDefault · Hover · Focus · DisabledInteractive states plus disabled. Focus is a colour change only in Figma; code keeps the native outline (C10). Gray and Black small hover colours are swapped in Figma; code follows the medium pattern (C9)
Left / Right IconOff · OnleadingIcon, trailingIcon
NoneNonehref (link or button) is code-only

Guidelines and related components

Use Link button for

  • Navigation: “View all lessons”, “Back to Biology 101”, links in running text.
  • Low-emphasis actions next to a field or list: “Resend code”, “Remove”.
  • Quiet actions on dark surfaces, such as “Undo” in a banner.

Don't use Link button for

  • The main action in a view. Use a filled Button.
  • Icon-only actions. Use Button with iconOnly.
  • Choosing between options. Use Button group.

Do and don't

DoUse an href when it goes somewhere. It can be opened in a new tab and is announced as a link.
Don'tDon't navigate from a button. A Button with onClick routing can't be opened in a new tab or copied.
DoKeep the main action a Button. The link button steps the alternative down in emphasis.
Don'tDon't make the main action a link button. Text alone is too quiet for the next step.

Your notes are saved. Open notes

DoUnderline links in running text. The underline marks the link without relying on colour.

Your notes are saved. Open notes

Don'tDon't rely on colour alone inside a sentence. People who can't tell blue from grey won't find it.
DoUse white only on dark surfaces. It's designed for Bg/Strong-950.
Don'tDon't use gray on dark surfaces. Sub-600 on Strong-950 is 2.68:1.

Common mistakes

  • Passing href="#" with an onClick to get a link look for an action. Leave out href; it renders a button.
  • Several “Remove” or “View all” links on one screen with no aria-label, so they all sound the same.
  • Opening a new tab with target="_blank" without saying so in the label.
  • Packing link buttons tightly side by side, which makes their 16–20px targets hard to hit.