A link that looks like a Button: it takes the reader to another page or place, such as "Back to the map" or "Open in Rejseplanen", where a Button runs an action on the screen it sits on.
When to use
Use LinkButton for a link shaped like a button, which takes the reader to another page or place, such as "Back to the map" or "Open in Rejseplanen".
Variants and states
| Rest | Hover | Pressed | Focus | |
|---|---|---|---|---|
| Solid | ||||
| Outline |
- Rest
- Hover
- Pressed
- Focus
Sizes
With icons
An icon before the label, in reading order, so it moves to the right in right-to-left text. Hidden from screen readers: the label names the link.
Props
| Name | Type | Default | Description |
|---|---|---|---|
href | string | — | Where the link goes: a URL or a path. Give it on every LinkButton, unless render gives a router's link that brings its own destination. |
variant | 'solid' | 'outline' | 'solid' | How much the link stands out: solid is filled, for the main place a panel leads to; outline is an edge only, for the links and actions beside it. |
size | 'small' | 'medium' | 'medium' | The link's height: medium is size.button, small is size.button-sm, for dense rows and panels. |
fullWidth | boolean | false | Whether the link fills the width of its container, such as a bottom sheet's main link. Sets data-full-width. |
startIcon | ReactNode | — | An icon before the label, in reading order, so it moves to the right in right-to-left text. Hidden from screen readers: the label names the link. |
endIcon | ReactNode | — | An icon after the label, in reading order. Hidden from screen readers: the label names the link. |
children | ReactNode | — | The label: the words that name where the link goes, in sentence case. Required in practice: a link with no words has no name. |
className | string | — | Reaches the root; className is merged after pn-LinkButton. |
style | CSSProperties | — | Reaches the root; className is merged after pn-LinkButton. |
ref | Ref<HTMLAnchorElement> | — | Reaches the root element. |
hrefstring, default —- Where the link goes: a URL or a path. Give it on every LinkButton, unless
rendergives a router's link that brings its own destination. variant'solid' | 'outline', default'solid'- How much the link stands out:
solidis filled, for the main place a panel leads to;outlineis an edge only, for the links and actions beside it. size'small' | 'medium', default'medium'- The link's height:
mediumissize.button,smallissize.button-sm, for dense rows and panels. fullWidthboolean, defaultfalse- Whether the link fills the width of its container, such as a bottom sheet's main link. Sets
data-full-width. startIconReactNode, default —- An icon before the label, in reading order, so it moves to the right in right-to-left text. Hidden from screen readers: the label names the link.
endIconReactNode, default —- An icon after the label, in reading order. Hidden from screen readers: the label names the link.
childrenReactNode, default —- The label: the words that name where the link goes, in sentence case. Required in practice: a link with no words has no name.
classNamestring, default —- Reaches the root;
classNameis merged afterpn-LinkButton. styleCSSProperties, default —- Reaches the root;
classNameis merged afterpn-LinkButton. refRef<HTMLAnchorElement>, default —- Reaches the root element.
Use it
import { LinkButton } from '@mylder/perron'Ask your agent
search_docs({ query: "LinkButton" })Keyboard and ARIA
From the browser's native link and Base UI's useRender, which adds no behaviour. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab, Shift+Tab | Moves focus to the link and away | The browser |
Enter | Follows the link: fires onClick, then goes to href unless the click is prevented | The browser |
Space | Nothing: Space scrolls the page, as on every link, and never follows it (CR-4) | The browser |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | link, native, from <a href> | Its text content: the label's children; aria-label or aria-labelledby from the caller win | Visited, by the browser; aria-current when the caller sets it |
startIcon, endIcon | none: aria-hidden="true" | none | none |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the label is text the link is named by; the icons are
aria-hidden, since an icon beside a word adds nothing the word doesn't say (Icons);variantcarries emphasis, not meaning; and the role says "link" where the shape says "button", so the label names where it goes (see Do and don't). - Every landmark and named region has an accessible name: none here; a link is neither.
- Every default label can be overridden: none here; LinkButton owns no strings.
- Every shape the type accepts renders, in every variant:
children,startIconandendIconrender a string, an element and a list alike, in everyvariantandsize. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; LinkButton keeps no hidden copy and collapses nothing. - Native semantics first: a native
<a href>, neverrole="button"and never Base UI'sButton(CR-1); arenderelement renders an<a>too. - Focus moves in reading order and returns on close: the link is one tab stop in document order, and opens nothing of its own to return from.
- Every target is at least
size.target-minsquare: the smallest target is a small LinkButton,size.button-sm(30 px) tall, and at least that wide with itsspace.300padding on each side and a word between. - Loops stop when the system asks for less motion: none here; LinkButton has no loop and no transition.
Owned strings
No owned strings: every word comes from the caller, as children.
LinkButton announces nothing of its own when target="_blank" opens a new tab, and owns no "Opens in a new tab" string: a link that leaves Mylder names its destination in its label ("Open in Rejseplanen"), and Mylder's own links open in the same tab.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Use LinkButton when pressing it goes somewhere: another page, another place in the app, another site | Use LinkButton to run an action, such as showing a line or closing a panel, with an onClick that prevents the link: that is a Button | review |
| DD-2 | must | Use Button for an action and LinkButton for navigation, even when they sit side by side and look alike | Render a link through Button's render={<a href="/map" />}: Base UI gives it role="button", and a screen reader hears a button that navigates | review |
| DD-3 | must | Name where the link goes, in sentence case: "Back to the map", "Open in Rejseplanen" | Write "Click here", "Read more", Title Case or an exclamation mark (Content design): the role already says it is a link | review |
| DD-4 | must | Give a router's link through render, such as render={<Link to="/map" />}, so the app's navigation runs and the look stays | Wrap a LinkButton in a router link, or a router link in a LinkButton: two nested <a> elements are invalid and make two tab stops | review |
| DD-5 | should | Use a plain <a> inside a sentence, where a link is part of the text | Put a LinkButton in running text: its height and padding break the line | review |
| DD-6 | must | Keep LinkButton in ink; give the screen's one main action to a brand Button | Restyle a LinkButton in the brand yellow: brand is never a link (Intents) | review |
| DD-7 | must | Leave a link out, or show a disabled Button, when there is nowhere to go yet | Render a LinkButton without href or render, or fake a disabled one with opacity: an <a> with no href is not a link, and disabled is a colour | review |
| DD-8 | must | Use IconButton for a control with an icon and no words | Pass only a startIcon and no children: the icon is aria-hidden, so the link has no name | review |