React Button Component Usage Examples
Practical React button examples — variants, sizes, icons, loading state, and rounded options using the Drivn Button component in your Next.js project.
A button is the smallest functional element in most React applications, which is exactly why its API compounds quickly across a codebase. A signup flow needs a primary Submit button, a card action row needs a secondary Edit plus a destructive Delete, and a modal needs an outline Cancel plus a default Continue — every screen replays the same five or six patterns with subtle variations. Drivn's Button component collapses those patterns into a single component with variant, size, rounded, loading, and icon props, so the entire vocabulary fits in your head and shows up in autocomplete.
This page walks through the examples you actually ship: the four variants side by side, the three sizes, left and right icon placement, the two corner radii, and a loading button wired to an async submit. Every snippet is copy-paste ready and assumes Drivn is already installed via the CLI. No runtime UI dependencies — the Button is pure React, Tailwind, and one Lucide icon for the spinner.
If you are exploring Drivn for the first time, start with the variants and sizes below. If you already use the Button and want focused recipes, jump to the loading and icon sections — each one maps to a concrete production use case you can drop into an existing screen.
Variants: default, secondary, outline, destructive
Drivn's Button ships four visual variants. default uses bg-foreground text-background with a subtle hover scale — ideal for primary actions. secondary renders a bordered card-colored button for supporting actions. outline is the same shape without the fill, for tertiary actions. destructive uses the destructive token for delete, cancel-subscription, or leave-organization calls to action.
Pick variants by action weight. Keep exactly one default per visible section so the primary intent stays unambiguous. Reach for destructive only for irreversible operations, and always pair it with a confirmation step — either a Dialog or a follow-up click. Use outline for navigation-adjacent actions like Cancel or Go back.
1 import { Button } from '@/components/ui/button' 2 3 <Button variant="default">Save changes</Button> 4 <Button variant="secondary">Preview</Button> 5 <Button variant="outline">Cancel</Button> 6 <Button variant="destructive">Delete account</Button>
Sizes: sm, md, lg
Three sizes cover the common call sites: sm for dense toolbars at 32 pixel height, md for forms and cards at 40 pixel, and lg for marketing hero calls-to-action at 48 pixel. The internal token differences are h-8 px-3 text-sm gap-1.5 for sm, h-10 px-4 text-sm gap-2 for md, and h-12 px-6 text-base gap-2 for lg. Type size steps from 14 pixel to 14 pixel to 16 pixel, not every step.
Mix sizes across the same page sparingly — two sizes visible in the same viewport tends to look intentional, three looks inconsistent. Use md as the baseline for forms (see the Input docs for matching heights) and step up to lg only when the Button is the primary visual element of the section.
1 <Button size="sm">Small</Button> 2 <Button size="md">Medium</Button> 3 <Button size="lg">Large</Button>
Icons: leftIcon and rightIcon
Both leftIcon and rightIcon accept a component reference (leftIcon={Plus}) or a JSX element (leftIcon={<Plus className="text-success" />}). The component form is the expected default — Drivn renders the icon at the size dictated by the active size prop. The JSX form is for the rare case where you need a custom color, a rotated icon, or a non-Lucide glyph.
Import Lucide icons directly — import { Plus, ArrowRight } from 'lucide-react' — and pass the imported reference. Drivn's Button will not place both a spinner and an icon at the same time; when loading is true the spinner takes the leading position and any leftIcon is hidden until loading clears. See the button-loading-state example for the full async pattern.
1 import { Plus, ArrowRight, Download } from 'lucide-react' 2 import { Button } from '@/components/ui/button' 3 4 <Button leftIcon={Plus}>Add item</Button> 5 <Button variant="secondary" rightIcon={ArrowRight}>Continue</Button> 6 <Button variant="outline" leftIcon={Download}>Download</Button>
Rounded: md or full
The rounded prop swaps between a 6-pixel medium radius and a full pill shape. The default is full, which suits marketing pages, CTA rows, and modern product UI. Pass rounded="md" when the Button sits inside a card grid or a dense toolbar where pill buttons would clash with rectangular siblings.
Both corners are controlled by the same Tailwind classes — rounded-md or rounded-full — applied to the base element. Change the token in the styles.rounded object if you want a different default for your whole app; Drivn components live in your repo so this is a one-file edit after running the CLI install. Consistency matters more than the specific radius — pick one default per product and stick with it.
1 <Button rounded="md">Medium radius</Button> 2 <Button rounded="full">Full radius (default)</Button>
Loading state tied to async work
Wire the loading prop to the boolean that tracks your async operation. Drivn sets aria-busy="true", disables pointer events, and swaps the leading icon for a Loader2 spinner from lucide-react. No width shift, no double-click risk, no extra wrapper. The classic pattern is a local useState<boolean>, flipped inside a try/finally around the async call so the button re-enables even on failure.
For forms, prefer the form library's submission state. When using React Hook Form, bind loading={formState.isSubmitting} instead of managing a parallel saving boolean — one source of truth, automatic error recovery, and less code. The dedicated button-loading-state example covers both patterns with full snippets and the React Hook Form version.
1 'use client' 2 import * as React from 'react' 3 import { Button } from '@/components/ui/button' 4 5 export function SubmitButton() { 6 const [saving, setSaving] = React.useState(false) 7 8 const onSubmit = async () => { 9 setSaving(true) 10 try { 11 await fetch('/api/save', { method: 'POST' }) 12 } finally { 13 setSaving(false) 14 } 15 } 16 17 return ( 18 <Button loading={saving} onClick={onSubmit}> 19 Save changes 20 </Button> 21 ) 22 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
The variant prop controls color and fill (default, secondary, outline, destructive), while rounded controls corner radius (md or full). They are independent — you can pair any variant with either corner style. For a consistent product design, pick one rounded value and stick with it across the app rather than mixing pill and rounded-medium buttons on the same screen.
Yes. Import the icon from lucide-react and pass the component reference: leftIcon={Plus}. Drivn renders it at the size dictated by the active size prop. If you need a JSX element with custom className or color, pass that instead — the Button accepts either form. Do not wrap the icon in a <span> or extra markup; the Button handles positioning.
Pass loading={true} while the async work runs. Drivn sets aria-busy, shows a spinner, and ignores click events internally until loading flips back to false. A try/finally around the async call ensures the button re-enables even on network errors. Avoid adding a separate disabled prop — loading already implies disabled at the DOM level.
Use md (the default) to match the 40-pixel height of Drivn's Input component. That keeps the submit button flush with the input row and avoids visual jumps between fields and the final action. Step up to lg only if the form is the entire focus of the page, such as a standalone signup, contact, or checkout screen.
Not directly through a prop — Drivn's Button does not support asChild polymorphism. For link-shaped buttons, render a Next.js <Link> and apply the button classes via the exported styles object. Since Drivn components live in your repo after CLI install, you can also edit the Button source to accept an as prop if your app leans heavily on link-as-button patterns.