15 shadcn Button Examples: Variants, Animations & Base UI Patterns

15 real-world shadcn button examples for Next.js 15 — every variant, animated pattern, Base UI snippet, and common mistake. Copy the code and ship faster.

AshFull-stack developer and UI/UX enthusiast.
Published Jun 17, 2026
Updated Jun 17, 2026
14 min read
shadcn/uiReactNext.jsButton ComponentUI ComponentsTypeScriptTailwind CSSAnimationsBase UIFrontend Development
15 shadcn Button Examples: Variants, Animations & Base UI Patterns

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:

bash

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

VariantWhen to use it
defaultPrimary action on any screen
destructiveDelete, remove, or irreversible actions
outlineSecondary actions placed next to a primary button
secondaryLower-priority actions that need visual weight
ghostMinimal actions inside tables, toolbars, or sidebars
linkInline text that triggers navigation — use with asChild

Sizes

SizeUse case
xsDense toolbars, badge-style controls — added recently
smTable row actions, compact card footers
defaultStandard forms, modals, and cards
lgHero sections and primary CTAs
iconIcon-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.

globals.css

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.

DestructiveButton.tsx

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.

ArchiveButton.tsx

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

bash
button.tsx

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.

globals.css

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.

button.tsx

Common shadcn Button Mistakes (and how to fix them)

  • Using variant="link" without asChild for navigation. The link variant renders a <button> styled to look like a link. It does not navigate. Use asChild with a Next.js Link component for actual page navigation.
  • Forgetting type="button" on non-submit buttons inside forms. Every button inside an HTML <form> defaults to type="submit" unless you specify otherwise. A Cancel or Reset button without type="button" submits the form on click.
  • Nesting <a> inside <button>. This is invalid HTML and produces inconsistent browser behaviour. The asChild pattern with Next.js Link avoids 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" without aria-label. Icon-only buttons carry no text for screen readers. Every size="icon" button requires a descriptive aria-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.
Launch your SaaS faster with a modern Shadcn UI template - Explore Free Templates

Frequently Asked Questions About the shadcn Button

A
Ash

Full-stack developer and UI/UX enthusiast.

Related Articles