Skip to content
UI

Button

Triggers an action or an event, such as submitting a form, opening a dialog or deleting a record.

@zenoxai/ui/components/buttonBase UI Markdown
function ButtonDemo() {
  return <Button>Save changes</Button>
}

Usage

import { Button } from "@zenoxai/ui/components/button"

<Button onClick={save}>Save changes</Button>

Use one default (primary) button per view for the main action. Everything else is outline, secondary or ghost. Write labels as a verb plus an object (“Save changes”, “Delete alert”), in sentence case.

Variants

<>
  <Button>Default</Button>
  <Button variant="secondary">Secondary</Button>
  <Button variant="outline">Outline</Button>
  <Button variant="ghost">Ghost</Button>
  <Button variant="destructive">Destructive</Button>
  <Button variant="link">Link</Button>
</>
Variant Use for
default The primary action of the view. One per view.
secondary A supporting action next to the primary one.
outline Actions in toolbars, filters and dialogs.
ghost Low-emphasis actions in dense areas: table rows, icon buttons.
destructive Irreversible actions. Confirm them with confirm() first.
link An action that reads as inline text.

Sizes

default (32px) is the size for forms and toolbars. sm (28px) and xs (24px) fit dense tables and filter bars; lg (36px) is for empty states and landing surfaces. Input, Select, Native Select, Number Input, Input Group and Combobox share the sm, default and lg heights, so a button and a field of the same size line up.

The icon follows the size name: 12px at xs, 14px at sm, 16px at default and lg, in the square icon-* sizes too.

<>
  <Button size="xs">Extra small</Button>
  <Button size="sm">Small</Button>
  <Button>Default</Button>
  <Button size="lg">Large</Button>
</>

With an icon

Mark the icon with data-icon="inline-start" or data-icon="inline-end" so the button tightens the padding on that side. An icon-only button uses an icon* size and must have an aria-label.

<>
  <Button>
    <PlusIcon data-icon="inline-start" />
    New alert
  </Button>
  <Button variant="outline">
    Continue
    <ArrowRightIcon data-icon="inline-end" />
  </Button>
  <Button variant="ghost" size="icon" aria-label="Delete alert">
    <Trash2Icon />
  </Button>
</>

Loading

Disable the button, set aria-busy="true" and put a Spinner in place of the icon while the action runs. A disabled button is dimmed to 50%; a busy one is not, so it reads as working rather than unavailable. Keep the label: it tells the user what is in progress.

<Button disabled aria-busy="true">
  <Spinner data-icon="inline-start" aria-hidden />
  Saving
</Button>

Navigation uses a link, not a button. To get the button look on a link, give the <a> the classes from buttonVariants(). Do not render an <a> through Button’s render prop: Base UI then gives it role="button", and screen readers announce a button that navigates.

<a href="#usage" className={buttonVariants({ variant: "outline" })}>
  Read the usage notes
</a>

Accessibility

  • A disabled button is removed from the tab order. Set focusableWhenDisabled when the user must still be able to reach it, for example to read a tooltip that explains why.
  • buttonVariants() returns the classes alone. Use it to give a link the button look; the element keeps its own role.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

Button

Button runs an action on the current page: submit a form, open a dialog, apply a filter. To navigate, use a link styled as a button instead: `<a href="..." className={buttonVariants({ variant: "outline" })}>`. Rendering an <a> through `render` keeps the look but Base UI gives the element role="button", so screen readers announce it as a button.

PropTypeDefault
focusableWhenDisabledboolean

Whether the button should be focusable when disabled.

false
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null

Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`.

default
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null

Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text.

default