ComponentsActions

Button

Compact actions, links, and toggles. Seven variants, sizes for common layouts, and Base UI focus and disabled behavior.

Clicked 0 times

Variants

Sizes

Text, icon, and pattern sizes. Touch screens get 44px targets.

Text

xs
sm
default
lg

Icon

icon-xs
icon-sm
icon
icon-lg

Patterns

backReturn to the previous page
onboardingFull-width auth and setup actions
budDraggable assistant launcher
bud-labeledLauncher with a name
chat-rowChat and project list rows
task-linkInline links that may wrap
card-titleClickable card headings
result-cardActions inside result cards
calendar-titleMonth picker heading
calendarCalendar toolbar actions
calendar-iconPrevious and next period
navTree and side navigation items
column-headerSortable table column
column-header-endSortable numeric column
table-linkLinks inside table cells
badgeFilter and scope chips

States

Disabled, busy, invalid, pressed, expanded, and inline icons.

Choose an owner before saving.

Action states

Pass state to show progress or say why a button cannot run. Busy and blocked buttons stay focusable.

Blocked, ready, busy

Choose an owner first.

Not saved yet.

Jump fix

Name a table to inspect first.

Link fix

An agent manager must activate this agent first.See setup steps

Pending labels

Action groups

Several actions share one state and one reason.

Someone changed execution settings while you edited them.

Refresh administration to load the latest settings.

Loading, error, and empty

Busy buttons stay disabled. Errors offer a retry. Focus returns after deletes.

No changes saved yet.

No sample files yet.

Toggles and toolbars

Selected options swap variants and set aria-pressed.

List view

Use nativeButton={false} and render for router or anchor links.

Base UI triggers

Menus, popovers, tooltips, and dialogs render Button through their render prop.

Sort: None. Draft kept.

Class-only styling

buttonVariants() styles elements that are not Button, and utilities override it.

Forms

Submit, reset, and an external submit using the form attribute.

Nothing submitted.

Application patterns

Sample data only. Sizes that fit common rows, cards, calendars, and trees.

Table

Sample files sorted by column
Customer notes7 KB
Launch plan42 KB
Roadmap128 KB

Calendar header

Rows and cards

Tree

Selected readme.md

Onboarding

Floating assistant

Assistant closed, bottom right

Usage

Button usageTSX
import { Button } from "@tulipfarm/sprout/button";

<Button onClick={save}>Save</Button>
<Button variant="outline" size="sm">
  <PlusIcon data-icon="inline-start" />
  Add item
</Button>
<Button variant="ghost" size="icon-sm" aria-label="Delete file">
  <TrashIcon />
</Button>

Variants are default, outline, secondary, ghost, destructive, link, and scope. Sizes are default, xs, sm, lg, and the square icon, icon-xs, icon-sm, and icon-lg. Give icon-only buttons an aria-label. Mark leading or trailing icons with data-icon="inline-start" or "inline-end" to tighten padding.

Links

Router linkTSX
<Button
  variant="outline"
  nativeButton={false}
  render={<Link to="/files" />}
>
  Open files
</Button>

With nativeButton=, Button renders a real link. It keeps link semantics, so screen readers announce a link and Enter follows it. A disabled link gets aria-disabled, leaves the tab order, and ignores clicks.

States

Invalid and pressedTSX
<Button aria-invalid aria-describedby="owner-error">Assign</Button>

<Button
  variant={selected ? "secondary" : "ghost"}
  aria-pressed={selected}
  onClick={toggle}
>
  Board
</Button>

Button styles disabled, aria-disabled, aria-busy, aria-invalid, aria-expanded, and aria-pressed. Your application owns the error text and the pressed state. For busy and blocked actions, pass state as shown below.

Action states

Ready, busy, and blockedTSX
import { Button, type ButtonState } from "@tulipfarm/sprout/button";

const state: ButtonState = sending
  ? { kind: "busy" }
  : !enabled
    ? {
        kind: "blocked",
        reason: "Turn on Enable execution first.",
        fix: { kind: "jump", label: "Go to Enable execution", target: "enable-execution" },
      }
    : { kind: "ready" };

<Button state={state} pendingLabel="Activating…" onClick={activate}>
  Activate saved instructions
</Button>

Pass state when an action can be busy or blocked. Busy setsaria-busy and, with pendingLabel, swaps the label for a spinner and that text. Busy and blocked buttons are disabled but stay in the tab order, so keyboard users reach them and hear the reason through aria-describedby.

With state, Button renders a data-slot="button-action" wrapper with data-state that also holds the reason, andclassName styles that wrapper. Keep passing a state, even{ kind: "ready" }, for the button's lifetime. Changing its kind then keeps the same button element and its focus. Long labels wrap in default, sm, and lgsizes.

Blocker fixesTSX
type ButtonBlockerFix =
  | { kind: "jump"; label: string; target: string }   // element id on this page
  | { kind: "link"; label: string; link: ReactElement } // your router <Link>
  | { kind: "action"; label: string; onClick: () => void };

A jump fix calls jumpTo, which scrolls to the element and focuses it, or its first focusable child. A linkfix renders your link through render. An actionfix runs a callback, such as Try again. Leave fix out to show only the reason. useActionControl(state) returns the same button props and reason for controls you build yourself.

Action groups

ActionGroupTSX
import { ActionGroup } from "@tulipfarm/sprout/button";

<ActionGroup
  state={reviewState}
  actions={[
    { label: "Keep my draft", variant: "outline", onClick: keepDraft },
    { label: "Use current settings", variant: "outline", onClick: adoptCurrent },
  ]}
/>

ActionGroup puts several actions under one state. Every button turns busy or blocked together, and one reason sits under the row.

Triggers and class-only styling

Base UI triggerTSX
import { Menu } from "@base-ui/react/menu";
import { Button, buttonVariants } from "@tulipfarm/sprout/button";

<Menu.Trigger render={<Button variant="outline" size="sm" />}>
  Sort
</Menu.Trigger>

<a className={buttonVariants({ variant: "ghost", size: "sm" })} href={url}>
  Open in calendar
</a>

Pass Button to any Base UI trigger or close part through render. The trigger supplies its ARIA state and Button styles it. Use buttonVariants() when another component owns the element. Tailwind utilities passed throughclassName override Button's own styles.

Pattern sizes

Pattern sizes fit common layouts: back (icon-only below 640px when its label is hidden), onboarding, bud and bud-labeled floating launchers, chat-row, task-link, card-title, result-card, calendar-title, calendar, calendar-icon, nav (tree rows), column-header, column-header-end, table-link, and badge. buttonVariantNames and buttonSizeNames list every option.

Behavior and accessibility

Buttons default to type="button"; pass type="submit" or "reset", and form for an external submit. Tab reaches every enabled button. Enter and Space activate buttons; Enter follows links. Focus shows a 2px ring. Coarse pointers get 44px minimum targets. Hover and press transitions take 120ms and stop when the user prefers reduced motion.

Button renders data-slot="button", data-variant, and data-size. Override data-slot when a wrapping component needs its own hook. Refs reach the rendered element.