Action Button
A button that performs an action when clicked and optionally asks for user confirmation.
import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function BasicActionButton() { return ( <ActionButton action={serverAction} requireAreYouSure> Do action </ActionButton> )}Installation
Section titled “Installation”This installation provides support for all official Shadcn icon libraries. The component is otherwise identical to the non-experimental installation.
Icon support is experimental and may not be fully stable since it uses internal Shadcn APIs.
This component relies on other items which must be installed first.
Copy and paste the following code into your project.
components/ui/action-button.tsx
"use client"
import { type ComponentProps, type ReactNode, useTransition } from "react"import { Button } from "@/components/ui/button"import { toast } from "sonner"import { LoadingSwap } from "@/components/ui/loading-swap"import { AlertDialog, AlertDialogDescription, AlertDialogTitle, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogTrigger,} from "@/components/ui/alert-dialog"
export function ActionButton({ action, requireAreYouSure = false, areYouSureDescription = "This action cannot be undone.", ...props}: ComponentProps<typeof Button> & { action: () => Promise<{ error: boolean; message?: string }> requireAreYouSure?: boolean areYouSureDescription?: ReactNode}) { const [isLoading, startTransition] = useTransition()
function performAction() { startTransition(async () => { const data = await action() if (data.error) toast.error(data.message ?? "Error") }) }
if (requireAreYouSure) { return ( <AlertDialog open={isLoading ? true : undefined}> <AlertDialogTrigger asChild> <Button {...props} /> </AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Are you sure?</AlertDialogTitle> <AlertDialogDescription> {areYouSureDescription} </AlertDialogDescription> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogAction disabled={isLoading} onClick={performAction}> <LoadingSwap isLoading={isLoading}>Yes</LoadingSwap> </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> ) }
return ( <Button {...props} disabled={props.disabled ?? isLoading} onClick={e => { performAction() props.onClick?.(e) }} > <LoadingSwap isLoading={isLoading} className="inline-flex items-center gap-2" > {props.children} </LoadingSwap> </Button> )}Update the import paths to match your project setup.
import { ActionButton } from "@/components/ui/action-button"import { updateSettings } from "@/actions/settings"
const settings = { name: "Kyle" }
<ActionButton action={updateSettings.bind(null, settings)} requireAreYouSure> Save Settings</ActionButton>Examples
Section titled “Examples”Default
Section titled “Default”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function DefaultActionButton() { return <ActionButton action={serverAction}>Do default behavior</ActionButton>}Require Are You Sure
Section titled “Require Are You Sure”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function AreYouSureActionButton() { return ( <ActionButton action={serverAction} requireAreYouSure areYouSureDescription="I can put anything I want here." > Do extra secure action </ActionButton> )}With Error
Section titled “With Error”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: true, message: "Something went wrong. Please try again later.", }}
export function ErrorActionButton() { return ( <ActionButton variant="destructive" action={serverAction}> Do error action </ActionButton> )}