Button
The action primitive: presses in and settles back — six variants, eight sizes, composes through render.
pnpm exec shadcn add @intelligo/buttonInside an app made by intelligo create, or any shadcn project that names the registry.
Route Error States, Team Settings, Usage, Invitation Accept, Workspace Settings, Auth: Login, Auth: Signup, Auth: Password Reset, Auth: Email Verification, Language Switcher, Trial Banner, Notifications, Pricing, Checkout, Billing Settings, Feature Gating, QR Payment (poll), Profile Settings, Onboarding, Artifacts, Privacy Settings, Chat, Chat Panel, Chat Widget, Chat Share
Alert dialog, Dialog, Dropdown menu, Input, Popover, Progress, Select, Sheet, Sidebar, Tabs, Textarea, Tooltip, Checkbox, Radio group, Switch, Collapsible, Command
Source
What shadcn add writes to components/ui/button.tsx. It is yours from then on.
/*
* The button: a control that presses in and settles back, over Base UI's
* Button so `render` keeps working for links and triggers. The press is a
* spring (`Press` from ai-motion, a client component) with an optional
* ripple; a button given its own `render` — a link, a trigger's element —
* keeps the CSS press. This module stays free of "use client" so a server
* component can import `buttonVariants`.
*/
import { Button as ButtonPrimitive } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority";
import { Press } from "@showcase/components/ui/ai-motion";
import { cn } from "@showcase/lib/utils";
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,scale,opacity] duration-fast ease-standard outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/40 active:not-aria-[haspopup]:scale-97 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 in-data-[slot=button-group]:active:scale-100 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/88",
outline:
"border-border bg-transparent hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground",
secondary:
"border-border bg-card text-card-foreground hover:border-input hover:bg-accent aria-expanded:bg-accent",
ghost:
"text-muted-foreground hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/16 focus-visible:border-destructive/40 focus-visible:ring-destructive/20",
link: "rounded-md text-primary underline-offset-4 hover:underline active:not-aria-[haspopup]:scale-100",
},
size: {
default:
"h-8 gap-1.5 px-3.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 px-3 text-xs has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-10 gap-2 px-5 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4",
icon: "size-8",
"icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-7",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function Button({
className,
variant = "default",
size = "default",
ripple = false,
render,
...props
}: ButtonPrimitive.Props &
VariantProps<typeof buttonVariants> & {
/** Spread a ripple from the press point (spring-pressed buttons only). */
ripple?: boolean;
}) {
const springs = render === undefined;
return (
<ButtonPrimitive
data-slot="button"
render={
springs ? (
<Press ripple={ripple} pressScale={variant === "link" ? 1 : 0.97} />
) : (
render
)
}
className={cn(
buttonVariants({ variant, size }),
// The spring owns the press; the CSS one stays for custom elements.
springs && "active:not-aria-[haspopup]:scale-100",
className
)}
{...props}
/>
);
}
export { Button, buttonVariants };Ready to try it? Three commands and the app runs against a stub model.