Icon Leading Button
Original · freeButtons with leading and trailing icons.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/btn-icon-leading.json"use client";
import { useState } from "react";
import { useReducedMotion } from "motion/react";
type IconProps = { className?: string };
function ArrowRightIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M5 12h14" />
<path d="m13 6 6 6-6 6" />
</svg>
);
}
function DownloadIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M12 3v12" />
<path d="m7 10 5 5 5-5" />
<path d="M5 21h14" />
</svg>
);
}
function SparkleIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M12 3v4M12 17v4M3 12h4M17 12h4" />
<path d="M12 8a4 4 0 0 0 4 4 4 4 0 0 0-4 4 4 4 0 0 0-4-4 4 4 0 0 0 4-4Z" />
</svg>
);
}
function PlusIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M12 5v14M5 12h14" />
</svg>
);
}
function ExternalIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M15 3h6v6" />
<path d="M10 14 21 3" />
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
</svg>
);
}
function TrashIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M4 7h16" />
<path d="M10 11v6M14 11v6" />
<path d="M5 7l1 13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-13" />
<path d="M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
);
}
function CopyIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<rect x={9} y={9} width={12} height={12} rx={2} />
<path d="M5 15V5a2 2 0 0 1 2-2h10" />
</svg>
);
}
function CheckIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.4}
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="m20 6-11 11-5-5" />
</svg>
);
}
function SpinnerIcon({ className }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2.4}
strokeLinecap="round"
className={className}
aria-hidden="true"
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
);
}
export default function BtnIconLeading() {
const reduceMotion = useReducedMotion();
const [copied, setCopied] = useState(false);
const [starred, setStarred] = useState(false);
const [loading, setLoading] = useState(false);
const [deployed, setDeployed] = useState(false);
const spin = reduceMotion ? "" : "bil-spin";
async function handleCopy() {
try {
await navigator.clipboard.writeText("npm i motion");
setCopied(true);
window.setTimeout(() => setCopied(false), 1800);
} catch {
setCopied(false);
}
}
function handleDeploy() {
if (loading || deployed) return;
setLoading(true);
window.setTimeout(() => {
setLoading(false);
setDeployed(true);
window.setTimeout(() => setDeployed(false), 2400);
}, 1600);
}
return (
<section className="relative w-full bg-white px-6 py-20 text-slate-900 dark:bg-slate-950 dark:text-slate-100 sm:px-10">
<style>{`
@keyframes bil-spin { to { transform: rotate(360deg); } }
@keyframes bil-pop { 0% { transform: scale(.6); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
.bil-spin { animation: bil-spin .7s linear infinite; }
.bil-pop { animation: bil-pop .28s ease-out; }
.bil-nudge { transition: transform .22s cubic-bezier(.34,1.56,.64,1); }
.bil-btn:hover .bil-nudge-trail { transform: translateX(3px); }
.bil-btn:hover .bil-nudge-lead { transform: translateX(-2px); }
@media (prefers-reduced-motion: reduce) {
.bil-spin { animation: none; }
.bil-pop { animation: none; }
.bil-nudge, .bil-btn:hover .bil-nudge-trail, .bil-btn:hover .bil-nudge-lead { transition: none; transform: none; }
}
`}</style>
<div className="mx-auto max-w-4xl">
<header className="mb-12">
<span className="inline-flex items-center gap-1.5 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300">
<SparkleIcon className="h-3.5 w-3.5" />
Buttons
</span>
<h2 className="mt-4 text-2xl font-bold tracking-tight sm:text-3xl">
Leading & trailing icon buttons
</h2>
<p className="mt-2 max-w-xl text-sm leading-relaxed text-slate-600 dark:text-slate-400">
Tactile, accessible buttons with icons on either side. Real clipboard, toggle,
and async loading states — every one is a genuine <code className="rounded bg-slate-100 px-1 py-0.5 text-[0.8em] dark:bg-slate-800"><button></code>.
</p>
</header>
{/* Variants */}
<div className="space-y-10">
<div>
<h3 className="mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 dark:text-slate-500">
Solid — leading & trailing
</h3>
<div className="flex flex-wrap items-center gap-4">
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-indigo-500 hover:shadow-md active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<SparkleIcon className="bil-nudge bil-nudge-lead h-4 w-4" />
Generate design
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl bg-slate-900 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-slate-800 hover:shadow-md active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200 dark:focus-visible:ring-offset-slate-950"
>
Continue
<ArrowRightIcon className="bil-nudge bil-nudge-trail h-4 w-4" />
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-emerald-500 hover:shadow-md active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<DownloadIcon className="bil-nudge bil-nudge-lead h-4 w-4" />
Export assets
</button>
</div>
</div>
<div>
<h3 className="mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 dark:text-slate-500">
Outline & ghost
</h3>
<div className="flex flex-wrap items-center gap-4">
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-semibold text-slate-800 shadow-sm transition-all duration-200 hover:border-slate-400 hover:bg-slate-50 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
>
<PlusIcon className="bil-nudge bil-nudge-lead h-4 w-4" />
New project
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-semibold text-indigo-700 transition-all duration-200 hover:bg-indigo-50 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-indigo-300 dark:hover:bg-indigo-500/10 dark:focus-visible:ring-offset-slate-950"
>
View live demo
<ExternalIcon className="bil-nudge bil-nudge-trail h-4 w-4" />
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl border border-rose-300 bg-white px-5 py-2.5 text-sm font-semibold text-rose-700 shadow-sm transition-all duration-200 hover:bg-rose-50 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-rose-500/40 dark:bg-slate-900 dark:text-rose-300 dark:hover:bg-rose-500/10 dark:focus-visible:ring-offset-slate-950"
>
<TrashIcon className="h-4 w-4" />
Delete workspace
</button>
</div>
</div>
<div>
<h3 className="mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 dark:text-slate-500">
Interactive states
</h3>
<div className="flex flex-wrap items-center gap-4">
{/* Copy — real clipboard */}
<button
type="button"
onClick={handleCopy}
className="bil-btn inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-semibold text-slate-800 shadow-sm transition-all duration-200 hover:bg-slate-50 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
>
{copied ? (
<CheckIcon className="bil-pop h-4 w-4 text-emerald-500" />
) : (
<CopyIcon className="h-4 w-4" />
)}
<span className="tabular-nums">
{copied ? "Copied!" : "npm i motion"}
</span>
</button>
{/* Toggle — aria-pressed */}
<button
type="button"
aria-pressed={starred}
onClick={() => setStarred((s) => !s)}
className={`bil-btn inline-flex items-center gap-2 rounded-xl border px-5 py-2.5 text-sm font-semibold shadow-sm transition-all duration-200 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950 ${
starred
? "border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-300"
: "border-slate-300 bg-white text-slate-800 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
}`}
>
<svg
viewBox="0 0 24 24"
fill={starred ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={`h-4 w-4 ${starred ? "bil-pop" : ""}`}
aria-hidden="true"
>
<path d="m12 3 2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 18l-5.8 3 1.1-6.5L2.6 9.8l6.5-.9L12 3Z" />
</svg>
{starred ? "Starred" : "Star"}
</button>
{/* Async loading */}
<button
type="button"
onClick={handleDeploy}
disabled={loading}
aria-live="polite"
className={`bil-btn inline-flex min-w-[10.5rem] items-center justify-center gap-2 rounded-xl px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed dark:focus-visible:ring-offset-slate-950 ${
deployed
? "bg-emerald-600 focus-visible:ring-emerald-500"
: "bg-violet-600 hover:bg-violet-500 hover:shadow-md focus-visible:ring-violet-500 disabled:opacity-80"
}`}
>
{loading ? (
<>
<SpinnerIcon className={`h-4 w-4 ${spin}`} />
Deploying…
</>
) : deployed ? (
<>
<CheckIcon className="bil-pop h-4 w-4" />
Deployed
</>
) : (
<>
<ArrowRightIcon className="h-4 w-4" />
Deploy to edge
</>
)}
</button>
</div>
</div>
<div>
<h3 className="mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 dark:text-slate-500">
Sizes
</h3>
<div className="flex flex-wrap items-center gap-4">
<button
type="button"
className="bil-btn group inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm transition-all duration-200 hover:bg-indigo-500 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<SparkleIcon className="h-3.5 w-3.5" />
Small
<ArrowRightIcon className="bil-nudge bil-nudge-trail h-3.5 w-3.5" />
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2 rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-indigo-500 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<SparkleIcon className="h-4 w-4" />
Medium
<ArrowRightIcon className="bil-nudge bil-nudge-trail h-4 w-4" />
</button>
<button
type="button"
className="bil-btn group inline-flex items-center gap-2.5 rounded-2xl bg-indigo-600 px-7 py-3.5 text-base font-semibold text-white shadow-sm transition-all duration-200 hover:bg-indigo-500 hover:shadow-lg active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
<SparkleIcon className="h-5 w-5" />
Large
<ArrowRightIcon className="bil-nudge bil-nudge-trail h-5 w-5" />
</button>
</div>
</div>
<div>
<h3 className="mb-4 text-xs font-semibold uppercase tracking-widest text-slate-500 dark:text-slate-500">
Disabled
</h3>
<div className="flex flex-wrap items-center gap-4">
<button
type="button"
disabled
className="inline-flex items-center gap-2 rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white opacity-50 shadow-sm disabled:cursor-not-allowed"
>
<DownloadIcon className="h-4 w-4" />
Export assets
</button>
<button
type="button"
disabled
className="inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-5 py-2.5 text-sm font-semibold text-slate-800 opacity-50 shadow-sm disabled:cursor-not-allowed dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
>
Continue
<ArrowRightIcon className="h-4 w-4" />
</button>
</div>
</div>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Shimmer Button
MITA dark pill button with a continuous conic-gradient light shimmer sweeping around its border.

Rainbow Gradient Button
MITA button wrapped in an animated multi-colour gradient border with a matching blurred underglow.

Interactive Hover Button
MITA pill button whose dot expands to fill the surface and reveals an arrow label on hover.

Pulsating Button
MITA solid button that emits a soft, continuously pulsing glow ring to draw the eye to the primary action.

Shiny Button
MITA frosted-glass button with a spring-animated light sweeping across its text and border.

Solid Set Button
OriginalA set of solid buttons: primary, secondary, success and danger, in three sizes.

Outline Set Button
OriginalOutline button variants across colours and sizes with a hover fill.

Ghost Set Button
OriginalGhost and text buttons with subtle hover backgrounds.

Soft Set Button
OriginalSoft, tinted buttons across semantic colours.

Gradient Set Button
OriginalGradient-filled buttons with a hover shift.
Icon Only Button
OriginalSquare and circular icon-only buttons with tooltips and aria-labels.

Pill Group Button
OriginalA segmented pill button group with a sliding active indicator.

