Web InnoventixFreeCode

Icon Leading Button

Original · free

Buttons with leading and trailing icons.

byWeb InnoventixReact + Tailwind
btniconleadingbuttons
Open

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
btn-icon-leading.tsx
"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 &amp; 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">&lt;button&gt;</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 &amp; 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 &amp; 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

motion

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 quote

Similar components

Browse all →