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 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.
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
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer 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
// 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
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.
hrefdecides 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.
| You pass | Renders | Role | Keys | Disabled |
|---|---|---|---|---|
href | <a href> | link | Enter | href removed, role="link" and aria-disabled="true"; leaves the tab order |
No href | <button>, type="button" by default | button | Enter / Space | Native disabled; leaves the tab order |
Tone × size
| Tone | Use for | Text | Hover | Figma Style |
|---|---|---|---|---|
gray | The default. Secondary links next to other content: “View all”, “Skip for now”. | Text/Sub-600 | Text/Strong-950 | Gray |
black | A link that needs more weight than gray, such as a section header's “View all”. | Text/Strong-950 | Text/Sub-600 | Black |
primary | Inline links and calls to action that should read as interactive blue. | State/Primary/Base | Primary/700 | Primary |
error | Quiet destructive actions: “Remove”, “Leave space”. | State/Error/Base | Error/700 | Error |
white | Dark surfaces only, such as a dark banner. | Text/White | No change | White |
Sizes
| Size | Row height | Type | Icons | Icon gap | Padding |
|---|---|---|---|---|---|
md | 20 | Label/Small (14/20, medium) | 20 × 20 | 4 (--nu-space-1) | None |
sm | 16 | Label/X Small (12px, medium) on a 16px line | 16 × 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
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
<LinkButton href="/spaces/biology-101" leadingIcon={<RiArrowLeftSLine />}>Back to Biology 101</LinkButton>
<LinkButton href="/library" tone="black" trailingIcon={<RiArrowRightSLine />}>View all lessons</LinkButton>
<LinkButton href="https://help.nuton.app" target="_blank" rel="noreferrer" tone="primary" trailingIcon={<RiExternalLinkLine />}>
Help Center (opens in a new tab)
</LinkButton>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
@media (hover: hover)). White doesn't change.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.
| Token | Gray | Black | Primary | Error | White |
|---|---|---|---|---|---|
--nu-link-color | Text/Sub-600 | Text/Strong-950 | State/Primary/Base | State/Error/Base | Text/White |
--nu-link-color-hover | Text/Strong-950 | Text/Sub-600 | Blue/700 | Red/700 | Text/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.
Back link
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.
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.
On a dark surface
Only use white on Bg/Strong-950 or a similarly dark surface. On white it's invisible.
Navigation that looks like a button
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
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | The visible label. Also the accessible name. |
href | string | — | 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. |
underline | boolean | false | Underlines the label (Figma Underline=On). |
leadingIcon | ReactNode | — | Icon before the label. Rendered aria-hidden. |
trailingIcon | ReactNode | — | Icon after the label. Rendered aria-hidden. |
disabled | boolean | false | Button: 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. |
ref | Ref<HTMLAnchorElement | HTMLButtonElement> | — | Forwarded to the rendered a or button. |
…rest | AnchorHTMLAttributes | 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
| Key | Behaviour |
|---|---|
| Tab | Moves focus to the link button. Disabled link buttons are skipped. |
| Enter | With href: follows the link. Without: activates the button. |
| Space | Without 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 property | Figma values | Code |
|---|---|---|
| Style | Gray · Black · Primary · Error · White | tone (default gray) |
| Size | Medium (20) · Small (16) | size: md · sm. Small underlined uses 14px in Figma; code uses 12px (C9) |
| Underline | Off · On | underline |
| State | Default · Hover · Focus · Disabled | Interactive 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 Icon | Off · On | leadingIcon, trailingIcon |
| None | None | href (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
Common mistakes
- Passing
href="#"with anonClickto get a link look for an action. Leave outhref; 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.