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,
},
});
As link
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. |