UI Kit/Button

Button

Starts an action. Six styles, four sizes, and it can render as a link.

View code
"use client";

import { Button } from "@/components/kit/button";
import { useState } from "react";

function Component() {
  const [open, setOpen] = useState<boolean>(false);

  return (
    <Button
      className="group"
      variant="outline"
      size="icon"
      onClick={() => setOpen((prevState) => !prevState)}
      aria-expanded={open}
      aria-label={open ? "Close menu" : "Open menu"}
    >
      <svg
        className="pointer-events-none"
        width={16}
        height={16}
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M4 12L20 12"
          className="origin-center -translate-y-[7px] transition-all duration-300 [transition-timing-function:cubic-bezier(.5,.85,.25,1.1)] group-aria-expanded:translate-x-0 group-aria-expanded:translate-y-0 group-aria-expanded:rotate-[315deg]"
        />
        <path
          d="M4 12H20"
          className="origin-center transition-all duration-300 [transition-timing-function:cubic-bezier(.5,.85,.25,1.8)] group-aria-expanded:rotate-45"
        />
        <path
          d="M4 12H20"
          className="origin-center translate-y-[7px] transition-all duration-300 [transition-timing-function:cubic-bezier(.5,.85,.25,1.1)] group-aria-expanded:translate-y-0 group-aria-expanded:rotate-[135deg]"
        />
      </svg>
    </Button>
  );
}

export { Component };

Built on Base UI Button. It handles keyboard focus and disabled behavior for you, and it can take the form of a link without losing its styles.

Installation

bunx --bun shadcn@latest add HarshitMahajan/UI/button

Copies the code to components/kit/button.tsx installs its packages, and adds the tokens it needs to your globals.css. Your existing shadcn components aren't touched.

Usage

import { Button } from "@/components/kit/button"
<Button variant="outline">Export</Button>

Composition

Button

Features

  • Six styles: default, secondary, outline, ghost, link and destructive
  • Four sizes, including a square one for icons
  • A focus ring that appears for keyboard users only
  • Renders as a link or your router's Link through the render prop
  • Exported ButtonProps, ButtonVariant and ButtonSize types

Styles

Set variant to change how much attention the button draws.

View code
import { Button } from "@/components/kit/button";

export function ButtonVariants() {
  return (
    <>
      <Button>Save changes</Button>
      <Button variant="secondary">Duplicate</Button>
      <Button variant="outline">Export</Button>
      <Button variant="ghost">Skip</Button>
      <Button variant="link">View details</Button>
      <Button variant="destructive">Delete</Button>
    </>
  );
}

Sizes

Use size for the height and padding. An icon button also needs an aria-label.

View code
import { Plus } from "lucide-react";

import { Button } from "@/components/kit/button";

export function ButtonSizes() {
  return (
    <>
      <Button size="sm">Small</Button>
      <Button>Default</Button>
      <Button size="lg">Large</Button>
      <Button size="icon" variant="outline" aria-label="Add item">
        <Plus size={16} strokeWidth={2} aria-hidden="true" />
      </Button>
    </>
  );
}

With an icon

Place an icon before or after the text. Icons never shrink and never catch clicks.

View code
import { Archive, Download } from "lucide-react";

import { Button } from "@/components/kit/button";

export function ButtonWithIcon() {
  return (
    <>
      <Button>
        <Archive className="-ms-1 me-2 opacity-60" size={16} strokeWidth={2} aria-hidden="true" />
        Archive
      </Button>
      <Button variant="outline">
        Download
        <Download className="-me-1 ms-2 opacity-60" size={16} strokeWidth={2} aria-hidden="true" />
      </Button>
    </>
  );
}

Loading

Disable the button and show a spinning icon while work is in progress.

View code
import { LoaderCircle } from "lucide-react";

import { Button } from "@/components/kit/button";

export function ButtonLoading() {
  return (
    <Button disabled>
      <LoaderCircle className="-ms-1 me-2 animate-spin" size={16} aria-hidden="true" />
      Saving
    </Button>
  );
}

Disabled

Disabled buttons drop to half opacity and ignore the pointer.

View code
import { Button } from "@/components/kit/button";

export function ButtonDisabled() {
  return (
    <>
      <Button disabled>Save changes</Button>
      <Button disabled variant="outline">
        Export
      </Button>
    </>
  );
}

As a link

Pass render with nativeButton={false} to get a real link with button styles.

View code
import { Button } from "@/components/kit/button";

export function ButtonAsLink() {
  return (
    <Button render={<a href="#install" />} nativeButton={false} variant="outline">
      Go to install
    </Button>
  );
}

Accessibility

Label icon-only buttons

A button with only an icon has no text to announce, so give it an aria-label that says what it does.

<Button size="icon" aria-label="Add item">
  <Plus aria-hidden="true" />
</Button>

Use a link for navigation

If clicking takes people to another page, render a link so they can open it in a new tab and screen readers announce it correctly.

API Reference

Button

Accepts everything Base UI Button accepts.

PropTypeDefaultDescription
variant"default" | "secondary" | "outline" | "ghost" | "link" | "destructive""default"The visual style.
size"default" | "sm" | "lg" | "icon""default"The height and padding.
renderReactElement | function-Render the button as another element, such as a link or your router's Link.
nativeButtonbooleantrueSet to false when render is not a button element.
disabledbooleanfalseStops the button from being used.
focusableWhenDisabledbooleanfalseKeeps a disabled button reachable by keyboard, such as while it loads.
classNamestring-Additional classes for the button.