Button

A clear, responsive action with quiet secondary states.

actioncontrol
Preview
Button preview

When to use

  • Any single action on a page, form, or dialog, such as Save, Continue, or Cancel.
  • Actions whose label changes in place, like Save to Saved, where the width should spring instead of jump.
  • Short async work where a spinner on the button is enough feedback, via loading.

When not to use

  • Use action-button when the button itself should show pending and success states after an async commit.
  • Use split-button when one default action has two to five close variants.
  • Use hold-to-confirm for destructive actions that need more than a single click.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "type": "registry:ui",
  "title": "Button",
  "description": "A clear, responsive action with quiet secondary states.",
  "categories": [
    "actions"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/button/button.tsx",
      "type": "registry:component",
      "target": "@components/manicat/button/button.tsx"
    },
    {
      "path": "registry/components/button/button.module.css",
      "type": "registry:component",
      "target": "@components/manicat/button/button.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/button",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/button",
    "markdown": "https://uiarc.dev/components/button/markdown",
    "tags": [
      "action",
      "control"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

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

export function ButtonExample() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>Save changes</Button>
      <Button variant="secondary">Cancel</Button>
      <Button variant="ghost">Learn more</Button>
      <Button variant="danger">Delete project</Button>
    </div>
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Destructive action Danger is reserved for the one irreversible choice; Cancel stays the easy way out.

Destructive action Danger is reserved for the one irreversible choice; Cancel stays the easy way out. preview

Delete project

This removes 12 deployments and every environment variable. It can't be undone.

Page header actions One primary action, a secondary one and a quiet ghost button, the usual hierarchy of a page header.

Page header actions One primary action, a secondary one and a quiet ghost button, the usual hierarchy of a page header. preview

Invoices

42 invoices this quarter

Async submit The loading state keeps the label and blocks double submits while a request is in flight.

Async submit The loading state keeps the label and blocks double submits while a request is in flight. preview

Sizes Small for dense toolbars, medium by default, large for hero calls to action.

Sizes Small for dense toolbars, medium by default, large for hero calls to action. preview

Keyboard interactions

KeysAction
Enter / SpaceActivates the button.

Accessibility

  • Renders a native button, so role and focus come for free.
  • Loading uses aria-busy and aria-disabled instead of disabled, so keyboard focus is not lost mid-action.
  • Icon-only buttons need an aria-label.
  • Disabled primary buttons fade as a whole. Disabled secondary and danger buttons keep their border and take a muted fill and label, and disabled ghost buttons take a muted label, so an unavailable action still reads as a button.

Motion

  • Presses scale to about 0.97 on a snappy spring; icon-sized buttons press slightly deeper.
  • A new label crossfades with a short blur while the width springs to fit.
  • Reduced motion drops the press scale and swaps labels with a plain fade.

Responsive behavior

  • Size is fixed by the size prop; the button never changes layout by breakpoint, so pick lg for primary touch targets.
  • Hover styles apply only on hover-capable fine pointers, so taps on touch screens do not stick in a hover state.

Performance

  • A ResizeObserver measures the label so the width can spring; one per button is cheap, but avoid hundreds in a long list.
  • Label changes animate opacity, transform, and a small blur only, with no layout thrash beyond the width spring.

Notes for AI

  • Default choice for any single action. Use action-button for dense icon toolbars and split-button when one action has close alternatives.
  • Pass a changing label (Save → Saved) as children to get the width morph for free.
  • Wrap in a Radix trigger with asChild; the press scale turns off automatically for popup anchors.