Action button
A compact button for frequent toolbar actions.
actiontoolbar
Preview
Action button preview
Live · keyboard ready
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
Live · keyboard ready
publish-a-post-an-async-button-that-shows-progress-and-success-on-its-own-while-the-promise-runs-.tsxtsx
Keyboard interactions
| Keys | Action |
|---|---|
| Enter / Space | Runs 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.