Skip to content
EMBOSS
Docs menu

Button

A momentary push button that rests embossed, depresses to flush while held, and springs back with a machined overshoot.

@emboss/button

Installation

pnpm dlx shadcn@latest add @emboss/button

Usage

example.tsx
import { Button } from "@/components/ui/button";

export function Example() {
  return (
    <Button variant="accent" size="lg">
      Engage
    </Button>
  );
}

Examples

Sizes

Three control heights plus square icon footprints. The lg size meets the 44px touch minimum.

Show code
sizes.tsx
import { Settings } from "lucide-react";
import { Button } from "@/components/ui/button";

export default function ButtonSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
      <Button size="icon" aria-label="Settings">
        <Settings />
      </Button>
    </div>
  );
}

With icon

Icons inherit a 16px footprint unless sized explicitly.

Show code
with-icon.tsx
import { ArrowDownToLine, Power } from "lucide-react";
import { Button } from "@/components/ui/button";

export default function ButtonWithIcon() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      <Button variant="accent">
        <Power /> Engage
      </Button>
      <Button>
        <ArrowDownToLine /> Download
      </Button>
    </div>
  );
}

Composed onto a link

The render prop hands the button's behavior and depth to any element.

Show code
as-link.tsx
import { buttonVariants } from "@/components/ui/button";

export default function ButtonAsLink() {
  return (
    <a href="/docs" className={buttonVariants({ variant: "accent" })}>
      Read the docs
    </a>
  );
}

API reference

Button

Extends the native button element. All standard button props are supported.

PropTypeDefaultDescription
variant"default" | "accent" | "danger" | "ghost" | "link""default"Visual role. Accent and danger carry color in addition to depth; ghost rests flat and presses into the surface; link renders as an inline link.
size"sm" | "md" | "lg" | "icon" | "icon-sm" | "icon-lg""md"Control height. The icon sizes render a square footprint for icon-only buttons.
renderReactElement | (props, state) => ReactElementSwaps the rendered element while keeping button behavior — the element must stay a real button. For links styled as buttons, put buttonVariants classes on the anchor instead.
disabledbooleanfalseDisables interaction and dims the control.

Keyboard

KeysAction
SpaceActivates and holds the press.
EnterActivates the button.

Source

View source — button.tsx
button.tsx
import { Button as BaseButton } from "@base-ui/react/button";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap focus-ring transition-[translate,box-shadow,background-color,color] duration-(--duration-release) ease-(--ease-release) select-none active:translate-[calc(var(--away-x)*1px)_calc(var(--away-y)*1px)] active:duration-(--duration-press) active:ease-(--ease-press) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default:
          "bg-surface-2 text-ink shadow-emboss-1 hover:shadow-emboss-2 active:shadow-flush",
        accent:
          "bg-accent text-accent-fg shadow-emboss-1 hover:shadow-emboss-2 active:bg-accent-deep active:shadow-flush",
        danger:
          "bg-danger text-danger-fg shadow-emboss-1 hover:shadow-emboss-2 active:shadow-flush",
        ghost:
          "text-ink hover:bg-surface-1 hover:shadow-flush active:bg-well active:shadow-deboss-1",
        link: "h-auto px-0 text-accent-ink underline-offset-4 hover:underline active:translate-none",
      },
      size: {
        sm: "h-(--control-sm) px-3",
        md: "h-(--control-md) px-4",
        lg: "h-(--control-lg) px-5 text-base",
        icon: "size-(--control-md)",
        "icon-sm": "size-(--control-sm)",
        "icon-lg": "size-(--control-lg)",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  },
);

export type ButtonProps = React.ComponentProps<typeof BaseButton> &
  VariantProps<typeof buttonVariants>;

/**
 * A momentary push button. Rests embossed, lifts on hover, and physically
 * depresses to flush while held — pointer or Space alike — then springs
 * back with a machined overshoot. Server-safe: the press physics are pure
 * CSS. For links styled as buttons, put `buttonVariants` classes on the
 * anchor so it keeps link semantics.
 *
 * @example
 * <Button variant="accent" size="lg">Engage</Button>
 * @example
 * // A link dressed as a button keeps link semantics:
 * <a href="/docs" className={buttonVariants({ variant: "accent" })}>Docs</a>
 */
function Button({ className, variant, size, ...props }: ButtonProps) {
  return (
    <BaseButton
      data-slot="button"
      className={cn(buttonVariants({ variant, size }), className)}
      {...props}
    />
  );
}

export { Button, buttonVariants };