Action button

A compact button for frequent toolbar actions.

actiontoolbar
Preview
Action button preview

When to use

  • A single async commit such as Save, Publish, or Submit where the result should appear on the button.
  • Call-to-action buttons where a trailing arrow invites the next step.
  • Flows where the button should reset to idle on its own after success, via resetAfterMs.

When not to use

  • Use button with loading for plain form submits that do not need a success state.
  • Use action-swap when the control toggles between lasting states.
  • Use hold-to-confirm when the action is destructive and needs a deliberate press.

shadcn CLI

Also installs (1)

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

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 { ActionButton } from "@/components/manicat/action-button/action-button";

export function ActionButtonExample() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <ActionButton label="Publish" onAction={async () => { await new Promise((done) => setTimeout(done, 900)); }} />
      <ActionButton label="Archive" onAction={async () => { await new Promise((done) => setTimeout(done, 1400)); }} />
    </div>
  );
}

Examples

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

Publish a post An async button that shows progress and success on its own while the promise runs.

Publish a post An async button that shows progress and success on its own while the promise runs. preview

Keyboard interactions

KeysAction
Enter / SpaceRuns the action.

Accessibility

  • Native button; the visible label is aria-hidden and a visually hidden copy of label names it.
  • Pending sets aria-busy and aria-disabled instead of disabled, so keyboard focus stays through the save.
  • A role="status" region announces the pending and success labels.

Motion

  • Changed letters rise from a soft blur while the width springs to fit; the arrow leaves forward and a check draws itself in.
  • Presses scale to about 0.97 on a snappy spring.
  • Reduced motion drops the press scale, width spring, and stroke draw, and swaps with an instant fade.

Responsive behavior

  • Width springs to fit pending and success labels, so keep them close in length inside tight toolbars.
  • Hover shadow applies only on hover-capable fine pointers; touch gets the press scale.

Performance

  • Every letter is a motion span with layout position animation plus a small blur; fine for a few buttons, not for dense tables.
  • A ResizeObserver drives the width spring.

Notes for AI

  • Use for a single async commit (save, publish, submit) where the result should show on the button itself.
  • Prefer button with loading for plain forms, and action-swap when the control toggles between lasting states.
  • Return the real promise from onAction; handle errors in onActionError since the button just resets.