Button
Trigger an action or event β submitting a form, opening a dialog, confirming a
change. Button is for actions that mutate state; for navigation that
changes the URL, use ButtonLink.
Import
import { Button, ButtonLink } from "@photon/pho-ui/components/button";
Sizes
Three sizes β sm, md, lg. The default is md.
<Button size="sm">Upload</Button>
<Button size="md">Upload</Button>
<Button size="lg">Upload</Button>
Variants
variant is a tight, semantic set: primary (the default high-emphasis
action), secondary, tertiary, and error for destructive confirmations.
There is no ghost or link β reach for tertiary for a quiet button, and
ButtonLink for navigation.
<Button variant="primary">Upload</Button>
<Button variant="secondary">Upload</Button>
<Button variant="tertiary">Upload</Button>
<Button variant="error">Upload</Button>
Shapes
shape is square (default), circle, or rounded. Icon-only buttons must
set svgOnly and an aria-label that names the action.
import { ArrowUp } from "@phosphor-icons/react";
<Button svgOnly aria-label="Upload" shape="square">
<ArrowUp />
</Button>
<Button svgOnly aria-label="Upload" shape="circle">
<ArrowUp />
</Button>
Prefix and suffix
prefix and suffix are generic slots placed before and after the label. Pass
an icon component (Plus) or an element (<Plus />).
import { ArrowLeft, ArrowRight, Plus } from "@phosphor-icons/react";
<Button prefix={ArrowLeft}>Back</Button>
<Button suffix={ArrowRight}>Continue</Button>
<Button variant="secondary" prefix={Plus} suffix={ArrowRight}>
New project
</Button>
Rounded
Combine shape="rounded" with the shadow prop for the pill button often used
on marketing pages.
<Button variant="secondary" shape="rounded" shadow>
Get started
</Button>
Loading
Pass loading instead of swapping in your own spinner β the button stays
readable, announces its busy state (aria-busy), and blocks interaction.
<Button loading>Saving</Button>
Disabled
<Button disabled>Upload</Button>
Disabled variants
<Button disabled>Primary</Button>
<Button disabled variant="secondary">Secondary</Button>
<Button disabled variant="tertiary">Tertiary</Button>
<Button disabled variant="error">Error</Button>
Link
Use ButtonLink for navigation. It shares every visual prop with Button and
adds href, rendering the host routerβs link through LinkProvider.
import { ArrowRight } from "@phosphor-icons/react";
<ButtonLink href="/docs" variant="secondary" suffix={ArrowRight}>
View docs
</ButtonLink>;
Props
- variant β
primaryΒ·secondaryΒ·tertiaryΒ·error(defaultprimary) - size β
smΒ·mdΒ·lg(defaultmd) - shape β
squareΒ·circleΒ·rounded(defaultsquare) - shadow β add an elevation shadow (pairs with
rounded) - prefix / suffix β a slot before / after the label (icon component or element)
- svgOnly β render an icon-only button; requires
aria-label - loading β show a spinner and block interaction
- disabled β native disabled state
- href (
ButtonLinkonly) β navigate viaLinkProvider
Best practices
- Use
Buttonfor actions that mutate state (deploy, save, delete); useButtonLinkfor navigation that changes the URL. When more than one related action shares a row, reach for a menu instead of stacking buttons. - The default
Buttonisprimary. Usesecondaryfor the supporting action anderrorfor destructive confirmations. There is noghost,link, orsuccessvariant by design β keep the emphasis set small. - Pass
loadingrather than swapping in a spinner, so the button stays focusable and announces the busy state to assistive tech. - Disable a button only when the action is impossible right now (missing input, insufficient permission); pair it with a tooltip that explains why.
- Title-case the label and name what happens:
Deploy Project,Invite Member,Rotate Key. Avoid bare verbs (Submit) and generic confirms (OK,Confirm). Destructive buttons follow Verb + Noun (Delete Project). - Icon-only buttons require both
svgOnlyandaria-label; the label names the action and target (Copy deployment URL), not the icon. Donβt set anaria-labelon a button that already has visible text.