Skip to content
Esc
navigateopen⌘Jpreview
On this page

Button

Displays a button or a component that looks like a button. Built on Base UI, styled with StyleX.

import { Button } from '@/components/ui/button';

export default function ButtonDemo() {
  return (
    <Button variant="outline">
      <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
        <path d={`M8 3v10M3 8h10`} />
      </svg>
      New project
    </Button>
  );
}

Install

npx shadcn@latest add @madeui/button

Usage

import { Button } from '@/components/ui/button';

<Button variant="outline">Button</Button>

Default

import { Button } from '@/components/ui/button';

export default function ButtonDefault() {
  return <Button>Button</Button>;
}

Secondary

import { Button } from '@/components/ui/button';

export default function ButtonSecondary() {
  return <Button variant="secondary">Secondary</Button>;
}

Outline

import { Button } from '@/components/ui/button';

export default function ButtonOutline() {
  return <Button variant="outline">Outline</Button>;
}

Ghost

import { Button } from '@/components/ui/button';

export default function ButtonGhost() {
  return <Button variant="ghost">Ghost</Button>;
}

Destructive

import { Button } from '@/components/ui/button';

export default function ButtonDestructive() {
  return <Button variant="destructive">Destructive</Button>;
}

Sizes

import * as stylex from '@stylexjs/stylex';

import { space } from '@/lib/constants.stylex';

import { Button } from '@/components/ui/button';

export default function ButtonSizes() {
  return (
    <div {...stylex.props(styles.row)}>
      <Button size="xs" variant="outline">Extra small</Button>
      <Button size="sm" variant="outline">Small</Button>
      <Button size="md" variant="outline">Medium</Button>
      <Button size="lg" variant="outline">Large</Button>
    </div>
  );
}

const styles = stylex.create({
  row: {
    alignItems: 'center',
    display: 'flex',
    flexWrap: 'wrap',
    gap: space.s2,
  },
});

Icon

Use size="icon" for a square, icon-only button. Always set aria-label.

import { Button } from '@/components/ui/button';

export default function ButtonIcon() {
  return (
    <Button size="icon" variant="outline" aria-label="Add">
      <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
        <path d={`M8 3v10M3 8h10`} />
      </svg>
    </Button>
  );
}

Icon sizes

icon pairs with md; use iconXs, iconSm, and iconLg to match the xs, sm, and lg text sizes.

import * as stylex from '@stylexjs/stylex';

import { space } from '@/lib/constants.stylex';

import { Button } from '@/components/ui/button';

export default function ButtonIconSizes() {
  return (
    <div {...stylex.props(styles.row)}>
      <Button size="iconXs" variant="outline" aria-label="Add">
        <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
          <path d={`M8 3v10M3 8h10`} />
        </svg>
      </Button>
      <Button size="iconSm" variant="outline" aria-label="Add">
        <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
          <path d={`M8 3v10M3 8h10`} />
        </svg>
      </Button>
      <Button size="icon" variant="outline" aria-label="Add">
        <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
          <path d={`M8 3v10M3 8h10`} />
        </svg>
      </Button>
      <Button size="iconLg" variant="outline" aria-label="Add">
        <svg width="16" height="16" viewBox={`0 0 16 16`} fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden>
          <path d={`M8 3v10M3 8h10`} />
        </svg>
      </Button>
    </div>
  );
}

const styles = stylex.create({
  row: {
    alignItems: 'center',
    display: 'flex',
    flexWrap: 'wrap',
    gap: space.s2,
  },
});

Loading

Render a <Spinner /> before the label and set disabled to show a loading state.

import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';

export default function ButtonLoading() {
  return (
    <Button disabled>
      <Spinner />
      Please wait
    </Button>
  );
}

Disabled

import { Button } from '@/components/ui/button';

export default function ButtonDisabled() {
  return <Button disabled>Disabled</Button>;
}

Rounded

Per-instance overrides go through the style prop — StyleX merges it last, so it always wins. No tailwind-merge, no !important.

import * as stylex from '@stylexjs/stylex';

import { radius } from '@/lib/tokens.stylex';

import { Button } from '@/components/ui/button';

// The style prop merges last — StyleX guarantees it wins over the
// component's own styles. No tailwind-merge, no !important.
export default function ButtonRounded() {
  return <Button style={styles.rounded}>Rounded</Button>;
}

const styles = stylex.create({
  rounded: {
    borderRadius: radius.full,
  },
});

The render prop swaps the underlying element while keeping the button styling — set nativeButton={false} when the element is not a <button>.

import { Button } from '@/components/ui/button';

// The render prop swaps the underlying element — here an <a> styled as a button.
export default function ButtonAsLink() {
  return (
    <Button variant="outline" render={<a href="#docs" />} nativeButton={false}>
      Read the docs
    </Button>
  );
}

API reference

Built on Base UI Button. The tables below cover the props this library adds or changes — every other prop is forwarded to the underlying Base UI part; see the Base UI Button API reference for the full list.

Button

Prop Type Default Description
variant 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' 'primary' Visual style.
size 'xs' | 'sm' | 'md' | 'lg' | 'icon' | 'iconXs' | 'iconSm' | 'iconLg' 'md' icon, iconXs, iconSm, iconLg render a square button.
disabled boolean false
style StyleXStyles StyleX styles merged last — always win over the component’s own styles.

Was this page helpful?