Skip to Content
Puzzle

Button

Buttons allow users to perform actions and navigate through your application.

They provide clear visual feedback and communicate the importance and intent of an action.

The Puzzle Button component supports both native buttons and links. When a href is provided, the component automatically renders a Puzzle Link instead of a native <button>.

Read more about the Link component here.

Import

import { Button } from "@puzzle-ui/ui";

Basic usage

Use the Button component to trigger an action.

390 × 140

By default, a Button renders a native HTML <button> element with type="button".

Variants

The variant prop controls the visual appearance and emphasis of the Button.

VariantDescription
primaryThe primary action. Use for the most important action in a section or page.
secondaryA secondary action with less visual emphasis.
outlineAn outlined button for actions that need a visible boundary without strong emphasis.
ghostA subtle button without a prominent background.
dangerAn action that may have destructive or irreversible consequences.
linkA button styled to look like a text link.
toggleA toggle-style button in its inactive state.
toggle-activeA toggle-style button in its active state.

The default variant is primary.

390 × 600

The link variant only changes the visual appearance. It does not turn the Button into a navigation link. To render an actual link, provide an href.

Sizes

Use the size prop to control the dimensions of the Button.

SizeDescription
xsExtra small
smSmall
mdMedium
lgLarge
iconIcon-only button

The default size is md.

390 × 600

The icon size is intended for buttons that contain only an icon. Always provide an accessible name, for example through aria-label.

Leading and trailing content

The leading and trailing props allow you to add content before or after the Button's children.

They are useful for icons, indicators, or other supplementary content.

390 × 400

Both props accept React nodes, so you can use any component or element as their content.

When loading is enabled, the leading content is replaced by the loading indicator. Trailing content remains visible.

Loading

Use the loading prop to indicate that an action is being processed.

390 × 200

When loading:

  • A loading indicator replaces the leading content.
  • The Button receives aria-busy="true".
  • Native buttons become disabled.
  • Link buttons prevent navigation and interaction.
  • Trailing content remains visible.

The loading state also prevents accidental repeated activation while an operation is in progress.

Disabled

Use the disabled prop when an action is currently unavailable.

390 × 200

The disabled state can also be activated through aria-disabled.

390 × 200

For native buttons, the component uses the native disabled attribute.

For links, the component uses aria-disabled, removes the link from the tab order, and prevents mouse and keyboard activation. This preserves the semantic distinction between a button and a link while providing consistent disabled behavior.

A Button is also considered disabled while loading is active.

Full width

Use fullWidth to make a Button fill the available width of its container.

390 × 200

This is useful for form actions, mobile layouts, and other situations where a full-width action is preferred.

The Button component automatically renders a Puzzle Link when an href is provided.

390 × 200

This means you can use the same component API for actions and navigation without manually switching between Button and Link components.

The component determines which element to render based on the presence of href:

ConfigurationRendered element
Without hrefNative <button>
With hrefPuzzle <Link>

The href property is required when using the link version of the Button.

Router-specific properties

The link version supports the properties of the Puzzle Link component.

Router-specific properties can be passed through componentProps.

<Button
  href="/dashboard"
  componentProps={
    {
      // Router-specific properties
    }
  }
>
  Go to dashboard
</Button>

This allows the Button to work with the routing integrations supported by Puzzle without coupling the Button component to a specific router.

Use a native Button when the user is performing an action, such as submitting a form, opening a dialog, or saving data.

Use href when the user is navigating to another location.

The visual link variant does not change the underlying HTML element. Navigation semantics are determined by href, not by variant.

API

Props

The Button component exposes a shared set of properties for both native buttons and links.

PropTypeDefaultDescription
variantButtonVariant"primary"Visual style of the Button.
sizeButtonSize"md"Size of the Button.
leadingReactNode—Content displayed before the children.
trailingReactNode—Content displayed after the children.
loadingbooleanfalseDisplays a loading indicator and prevents activation.
disabledbooleanfalseDisables the Button.
fullWidthbooleanfalseMakes the Button fill its container's width.
classNamestring—Additional CSS class names.
childrenReactNode—Content displayed inside the Button.

Native button props

When href is not provided, the component renders a native HTML <button>.

All standard React button attributes are supported, including:

  • type
  • onClick
  • onFocus
  • onBlur
  • name
  • value
  • form
  • aria-\*
  • data-\*

The type property defaults to "button".

When href is provided, the component renders a Puzzle Link.

The link version supports the properties of the Puzzle Link component, including:

  • href
  • target
  • rel
  • onClick
  • aria-*
  • data-*
  • componentProps

The href property is required for this version.

TypeScript types

The component exposes the following types:

type ButtonVariant =
  | "primary"
  | "secondary"
  | "outline"
  | "ghost"
  | "danger"
  | "link"
  | "toggle"
  | "toggle-active";

type ButtonSize = "xs" | "sm" | "md" | "lg" | "icon";

type ButtonProps = ButtonNativeProps | ButtonLinkProps;

ButtonProps is a discriminated union based on the presence of href.

This ensures that native button attributes and link attributes are correctly typed and that incompatible properties cannot accidentally be combined.

For example, type is only available on the native button version, while href is required for the link version.

Accessibility

The Button component includes accessibility behavior for both native buttons and links.

Keyboard interaction

  • Native buttons support the standard browser keyboard interactions.
  • Link buttons support normal link keyboard navigation.
  • Disabled links cannot be activated using Enter or Space.
  • Disabled links are removed from the tab order.

Focus visibility

Buttons provide a visible focus indicator when navigating with a keyboard through :focus-visible.

Loading state

When loading:

  • The component exposes aria-busy="true".
  • The loading indicator is hidden from assistive technology.
  • The Button cannot be activated again until loading ends.

Disabled state

The component uses the native disabled attribute for buttons and aria-disabled for links.

This ensures that the underlying element retains the correct semantic meaning.

Icon-only buttons

When using size="icon", always provide an accessible name.

Do not rely on the visual appearance of an icon to communicate its meaning to assistive technology.

Reduced motion

The Button respects the user's reduced-motion preference. Transitions and loading animations are adjusted when prefers-reduced-motion: reduce is active.

;