Propel

Button

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

Show code
import { Button } from "@makeplane/propel/components/button";

export default function BasicDemo() {
  return <Button label="Button" variant="primary" size="sm" stretch="auto" />;
}

Installation

import { Button } from "@makeplane/propel/components/button";

Usage

import { Button } from "@makeplane/propel/components/button";

export default function BasicDemo() {
  return <Button label="Button" variant="primary" size="sm" stretch="auto" />;
}

Examples

With an icon

Pass icon and pick its side with iconPosition (start by default).

Show code
import { Button } from "@makeplane/propel/components/button";
import { ArrowNarrowRightOutline, AddOutline } from "@makeplane/propel/icons";

export default function WithIconDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button label="New" variant="primary" size="sm" stretch="auto" icon={<AddOutline />} />
      <Button
        label="Continue"
        variant="secondary"
        size="sm"
        stretch="auto"
        icon={<ArrowNarrowRightOutline />}
        iconPosition="end"
      />
    </div>
  );
}

Variants

Variant sets the visual weight and palette: primary, secondary, tertiary, and ghost, plus danger and danger-outline for destructive actions.

Show code
import { Button } from "@makeplane/propel/components/button";

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button label="Primary" variant="primary" size="sm" stretch="auto" />
      <Button label="Secondary" variant="secondary" size="sm" stretch="auto" />
      <Button label="Tertiary" variant="tertiary" size="sm" stretch="auto" />
      <Button label="Ghost" variant="ghost" size="sm" stretch="auto" />
      <Button label="Danger" variant="danger" size="sm" stretch="auto" />
      <Button label="Danger outline" variant="danger-outline" size="sm" stretch="auto" />
    </div>
  );
}

Sizes

size sets the height and everything that follows from it — text, glyph, padding, and radius (xs 20 / sm 24 / md 28 / lg 32 px). There is no default: every call site picks one. xs is inline chrome only: it ships a synthesized 24px pointer target, which is why the grouped families have no xs rung at all — ButtonGroup runs sm–md and SplitButton sm–lg, since their segments touch and adjacent targets would intersect. See Sizing for the full ladder.

Show code
import { Button, type ButtonSize } from "@makeplane/propel/components/button";

const SIZES: ButtonSize[] = ["xs", "sm", "md", "lg"];

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {SIZES.map((size) => (
        // The label carries a word as well as the rung: with a two-character label every button
        // hits its min-width floor (internal/button-geometry.ts) and the padding ramp stops
        // being visible.
        <Button key={size} label={`Button ${size}`} variant="primary" size={size} stretch="auto" />
      ))}
    </div>
  );
}

Loading

The loading state shows a spinner in the icon slot, sets aria-busy, and blocks interaction while staying focusable.

Show code
import { Button } from "@makeplane/propel/components/button";

export default function LoadingDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button label="Saving" variant="primary" size="sm" stretch="auto" loading />
      <Button label="Inviting member" variant="secondary" size="sm" stretch="auto" loading />
      <Button label="Please wait" variant="tertiary" size="sm" stretch="auto" loading />
    </div>
  );
}

Full width

stretch="full" makes the button fill its container, e.g. a form row or a mobile CTA.

Show code
import { Button } from "@makeplane/propel/components/button";

export default function FullWidthDemo() {
  return (
    <div className="flex w-64 flex-col gap-2">
      <Button label="Full-width" variant="primary" size="sm" stretch="full" />
      <Button label="Full-width outline" variant="secondary" size="sm" stretch="full" />
    </div>
  );
}

Disabled

disabled is the hard, non-focusable disabled state; unlike loading, it removes the button from the tab order.

Show code
import { Button } from "@makeplane/propel/components/button";

export default function DisabledDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button label="Save changes" variant="primary" size="sm" stretch="auto" disabled />
      <Button label="Delete workspace" variant="danger-outline" size="sm" stretch="auto" disabled />
    </div>
  );
}

render={<a href=… />} swaps the underlying element and nativeButton={false} applies Base UI’s non-native button semantics: the control is announced as a button while the <a> keeps native navigation.

Show code
import { Button } from "@makeplane/propel/components/button";

export default function AsLinkDemo() {
  return (
    <Button
      label="Open reports"
      variant="secondary"
      size="sm"
      stretch="auto"
      nativeButton={false}
      render={<a href="#reports" />}
    />
  );
}

API Reference

Button

The ready-made Button: grafts Base UI's Button behavior onto the styled Button element and lays out an optional icon beside the label (iconPosition), swapping it for the loading spinner in the same slot. Content — the label, icon, and loading state — is not a variant.

PropTypeDefaultDescription
variant(required)"primary" | "secondary" | "tertiary" | "ghost" | "danger" | "danger-outline"—
size(required)"xs" | "sm" | "md" | "lg"—
stretch(required)"auto" | "full"—
label(required)string—Visible button label.
disabledboolean—Hard, non-focusable native disabled state (loading stays focusable instead).
type"button" | "submit" | "reset""button"The button's form behavior.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
nativeButtonboolean—Set false when render swaps the underlying tag away from <button> (e.g. a <div> or an <a>): Base UI then adds role="button", tab focus, and Enter/Space activation.
iconReactNode—Icon rendered beside the label (inline-start by default), e.g. <Icon icon={Plus} />.
iconPosition"start" | "end""start"Which side of the label the icon sits on. The loading spinner takes the same slot.
loadingboolean—Shows a spinner in the icon slot, sets aria-busy, and makes the button non-interactive.