Links and buttons
Choosing between TextLink, a link button and a plain button.
The principle
Link blue means "this takes you somewhere". It is reserved for navigation that reads as content. Two questions decide every case:
- What is it? If it navigates (changes the URL), it renders an
<a>. If it does something on this page, it renders a<button>. Screen readers announce them differently, and only a link can open in a new tab. - How should it look? Link blue is for navigation that reads as content: a link inside a sentence, or a standalone reference. UI navigation and every action use a button style.
Which to use
| What it is | Renders | Use | Look | Examples |
|---|---|---|---|---|
| Navigation inside a sentence | <a> | TextLink | Link blue, underlined, takes the sentence's size and weight | "Add a payment method in billing settings", "Don't have an account? Sign up" |
| A standalone reference link | <a> | Button variant="link" with to | Link blue | Settings "Docs" links |
| Navigation that is part of the UI | <a> | Button variant="plain" with to, or the component's own link (LinkCell, row links, LinkCard) | Gray, primary on hover | "View all", "Configure", PR and ticket links, "Back to login" |
| An action: opens a modal, resends, switches a step, selects, saves | <button> | Button variant="plain", or another button variant (in an Alert, its #actions slot) | Never link blue | "Forgot password?", "Resend code", "Select all", "Edit", "End trial" |
| A Banner or Alert call-to-action that navigates | <a> | TextLink with to | The banner or alert color | "Manage billing", "Change seat limit" |
Links that leave the app
One icon, i-lucide-arrow-up-right, means "this leaves the app": a new tab, or a redirect to another site. It follows the common ground between Carbon(opens in new tab), GOV.UK(opens in new tab) and NN/g(opens in new tab).
- Standalone links that open a new tab (LinkCell external, plain or link buttons to PRs, tickets, docs) show the arrow. Buttons take it as
trailing-icon; LinkCell adds it itself. - Menu items that open a new tab (
target: '_blank') take the arrow as their leadingicon. DropdownMenu adds it when the item has none and appends the hidden "(opens in new tab)" text to the label. - Links inside a sentence show the arrow too. TextLink adds it after the text, sized to the sentence.
- Every new-tab link is announced: TextLink, Button and LinkCell add hidden "(opens in new tab)" text for screen readers.
- In-app navigation never shows the arrow, and
i-lucide-external-linkis not used for links: one icon, one meaning. - Open a new tab only when it keeps the user's place (docs or an external tool opened mid-flow, a PR opened from a table). Everything in-app opens in the same tab.
Guidelines
- Give a button that only navigates a
to, not@click="navigateTo(...)". A real link can open in a new tab and is announced as a link. - Use TextLink inside a sentence, a banner message or a form hint. A standalone "View all" or metadata link is Button
variant="plain"withto. - Inside a Table, use its own links:
createLinkColumn,createRowLinkColumnor<LinkCell>. - Do not style an action as a link. "Forgot password?", "Resend code" and "Select all" act on the page, so they are
variant="plain", not blue. - Do not use
Button variant="link"withoutto. It logs a dev warning pointing tovariant="plain". - Do not use
TextLinkwithoutto, even in a Banner or Alert. An action there is aButton; TextLink logs a dev warning withoutto.
Examples
Navigation inside a sentence
TextLink: link blue and underlined, sized by the sentence.
Add a payment method in billing settings to keep scanning.
<template> <p> Add a payment method in <TextLink to="/billing">billing settings</TextLink> to keep scanning. </p></template>Standalone reference
Button variant="link" with to.
<template> <Button variant="link" size="xs" to="https://www.hacktron.ai/docs" target="_blank" trailing-icon="i-lucide-arrow-up-right"> Docs </Button></template>Navigation that is part of the UI
Button variant="plain" with to: gray, primary on hover.
<template> <div class="flex items-center justify-between"> <span>Recent scans</span> <Button variant="plain" size="xs" to="/scans">View all</Button> </div></template>An action
Button variant="plain" with @click: never link blue, even inside a sentence.
2 filters applied.
<template> <p> 2 filters applied. <Button variant="plain" size="sm" @click="clearFilters">Clear filters</Button> </p></template>Alert call-to-action
TextLink with to in a colored Alert takes the alert color. An action there is a Button in the #actions slot.
<template> <Alert variant="warning" title="Seat limit reached"> <template #description> You have used all available seats. <TextLink to="/billing">Upgrade plan</TextLink> </template> </Alert></template>