This guide covers every built-in shadcn button variant, 5 animation patterns, a Base UI alternative, and the production mistakes that break forms and accessibility. Each example targets Next.js 15 App Router. shadcn/ui supports both Radix UI and Base UI as primitive backends — fully documented since January 2026.
Install the component in one command:
What is the shadcn/ui Button component, and how does it work?
The shadcn/ui Button is an accessible, fully editable button component. It uses class-variance-authority (CVA) for variant management and either the Radix UI or Base UI Slot primitive for the asChild composition pattern. You own the source code at components/ui/button.tsx — no locked node_modules, no abstracted config. A default shadcn/ui project setup adds roughly 20–50 KB to the bundle, depending on how many components you include.
Since December 2025, npx shadcn create lets you pick your primitive at setup. Choose Radix UI (the original) or Base UI (the newer alternative from the MUI team). Both produce components that look and behave identically. Only the underlying implementation changes.
Built-in shadcn Button variants and sizes at a glance
shadcn/ui ships with 6 variants and 5 sizes. The table below shows each one and its correct use case.
Note: ghost adds a subtle background tint on hover and link adds an underline — both are intentionally minimal by design.
Variants
| Variant | When to use it |
|---|---|
default | Primary action on any screen |
destructive | Delete, remove, or irreversible actions |
outline | Secondary actions placed next to a primary button |
secondary | Lower-priority actions that need visual weight |
ghost | Minimal actions inside tables, toolbars, or sidebars |
link | Inline text that triggers navigation — use with asChild |
Sizes
| Size | Use case |
|---|---|
xs | Dense toolbars, badge-style controls — added recently |
sm | Table row actions, compact card footers |
default | Standard forms, modals, and cards |
lg | Hero sections and primary CTAs |
icon | Icon-only buttons — always pair with aria-label |
Note on cursor behaviour: Tailwind v4 switched buttons from cursor: pointer to cursor: default. Add the snippet below to globals.css to restore pointer cursors, or pass --pointer to npx shadcn@latest init.
15 shadcn Button Examples Used in Real Projects
1. Default Submit Button Inside a Form
The most common shadcn button pattern in any Next.js project. A primary button sits at the end of a shadcn form and submits the data.
The type="submit" attribute is required. Without it, clicking the button inside a <form> may not trigger React Hook Form validation. Pair this with the disabled prop during submission to block double-clicks.
2. Loading State Button with Spinner
shadcn/ui ships a dedicated Spinner component that picks up your theme tokens automatically. Use it to replace the label during async operations so users know the action is in progress.
3. Destructive Button Wired to an AlertDialog
Use the destructive variant for any action that cannot be reversed. Place it inside a shadcn AlertDialog so a single accidental click cannot cause data loss. Install it with npx shadcn@latest add alert-dialog if not yet in your project.
The AlertDialogAction handles the confirmed delete. The trigger button opens the dialog — the destructive action never fires without confirmation.
4. Button as a Next.js 15 App Router Link
The asChild prop passes all Button styles and behaviour to any child element. This is the correct pattern for a styled button that navigates — it avoids nesting an <a> tag inside a <button>, which is invalid HTML.
In Next.js 15 App Router, Link renders directly as an <a> tag. The asChild prop merges the Button's Tailwind classes onto that element. No wrapper <div>, no HTML violations.
5. Icon Button with Tooltip (Accessible)
Use size="icon" in data tables and toolbars where space is limited. Always add aria-label — screen readers cannot describe an icon-only button without it.
Keep icons at h-4 w-4 inside default and sm buttons. Larger icons push the button height out of alignment with adjacent form elements. For a full deep-dive into all tooltip options, see the shadcn tooltip component guide.
6. Button Group for Primary and Secondary Actions
A standard modal footer or settings page layout: one outline secondary action and one default primary action side by side.
The type="button" on the Cancel button is critical. Every button inside an HTML <form> defaults to type="submit". Without it, Cancel triggers form submission.
7. Server Action Button in Next.js 15 App Router
A button that triggers a Next.js Server Action without any client-side JavaScript or useForm setup. This pattern works without JavaScript enabled and handles simple mutations — archiving, toggling status, or soft-deleting records.
For complex mutations with optimistic UI, combine this with useActionState from React 19. It exposes a pending boolean that replaces the manual useState approach.
8. Disabled Button with Tooltip Explaining Why
Show users why an action is unavailable — never just hide the button. This pattern appears on multi-step forms and permission-gated dashboards.
The <span> wrapper is not optional. Disabled HTML elements block pointer events, so the Tooltip would never open without it.
9. Base UI Button — When and How to Use It
As of January 2026, shadcn/ui supports Base UI as a drop-in alternative primitive backend. The Base UI Button uses @base-ui-components/react instead of the Radix UI Slot primitive. The API stays identical — your existing Button import and props do not change.
Base UI and Radix UI produce the same visual output
Use Base UI when your project already depends on @base-ui-components/react for other components — it removes the Radix peer dependency and reduces bundle duplication. If your project uses Radix UI components (Dialog, Popover, DropdownMenu), stick with the Radix version.
Animated shadcn Button Patterns
Standard Tailwind transition-colors covers basic hover feedback. The 5 patterns below add motion that is meaningful — each one signals a different kind of interaction.
10. Shimmer / Shine Effect Button
A moving light sweep across the button surface. Use this for premium CTAs and upgrade prompts where you need visual distinction without changing colour. The shimmer runs entirely in CSS — no JavaScript, no requestAnimationFrame, no layout thrash.
11. Three-State Async Button
Shows three distinct async states — idle, loading, and success — to reduce perceived wait time on slow API calls. The success state auto-resets after 2 seconds.
12. Magnetic Hover Effect Button
The button shifts slightly toward the cursor on hover, creating a tactile, physical feel on primary CTAs. Set the multiplier (0.25) between 0.1 and 0.4 — above 0.4 the movement becomes disorienting.
13. Slide-In Icon on Hover
An arrow icon slides in from the right on hover while the label text shifts left. This signals forward progression — ideal for "Get started" and "View demo" CTAs. The group Tailwind utility coordinates the label shift and icon appearance from a single hover state on the parent. No JavaScript required.
14. Pulse / Glow CTA Button
A slow pulse animation draws attention to the most important action on the page. Use it on a single primary CTA — never on multiple buttons simultaneously. The glow colour reads from the --primary CSS token, so it automatically matches any shadcn theme.
The prefers-reduced-motion wrapper disables the animation for users who have it off at the OS level — this is required for WCAG 2.1 conformance.
15. Custom CVA Brand Variant
Adding a new permanent variant is the cleanest approach when one colour or style appears repeatedly. Open components/ui/button.tsx and extend the CVA variants object directly. TypeScript infers the new variant immediately — no extra type declarations needed.
Common shadcn Button Mistakes (and how to fix them)
- Using
variant="link"withoutasChildfor navigation. Thelinkvariant renders a<button>styled to look like a link. It does not navigate. UseasChildwith a Next.jsLinkcomponent for actual page navigation. - Forgetting
type="button"on non-submit buttons inside forms. Every button inside an HTML<form>defaults totype="submit"unless you specify otherwise. A Cancel or Reset button withouttype="button"submits the form on click. - Nesting
<a>inside<button>. This is invalid HTML and produces inconsistent browser behaviour. TheasChildpattern with Next.jsLinkavoids it entirely. - Not disabling the button during async operations. Without a disabled state, users can trigger multiple concurrent API requests. Always set
disabled={isLoading}during async calls. - Using
size="icon"withoutaria-label. Icon-only buttons carry no text for screen readers. Everysize="icon"button requires a descriptivearia-label. - Applying animated variants to multiple buttons at once. The glow and magnetic patterns draw the eye. Using them on more than one button per page removes the contrast that makes them effective.
Related Posts
- shadcn card component guide
- shadcn tooltip component guide
- shadcn accordion component guide
- how to build a landing page with Next.js and shadcn/ui
- shadcn tabs component guide




