Appearance
ActionPanel
ActionPanel is a presentational card component for rendering a vertical list of actions (icon + label). It is designed for ERP detail pages and side panels where available actions are often backend-driven.
Import
tsx
import { ActionPanel } from "@tailor-platform/app-shell";Basic Usage
tsx
function BasicUsage() {
return (
<ActionPanel
title="Actions"
actions={[
{
key: "create-invoice",
label: "Create new sales invoice",
icon: <Receipt />,
onClick: () => {},
},
{
key: "view-docs",
label: "View documentation",
icon: <ExternalLink />,
onClick: () => {},
},
]}
/>
);
}Props
Generated from the type surface — first-party props only.
ActionPanelProps
| Prop | Type | Default | Description |
|---|---|---|---|
actions | ActionItem[] | - | List of actions to render |
className? | string | - | Additional CSS classes for the card container |
emptyContent? | ReactNode | - | Content shown with consistent padding when actions is empty. Defaults to a localized "No actions available" message. Strings use the default body text styling; other React nodes render unchanged. Pass null to hide the empty content and its additional vertical spacing while keeping the card and title. |
title | string | - | Card title |
Exports
ActionPanel— functionActionPanelProps— interface
ActionItem
| Property | Type | Default | Description |
|---|---|---|---|
key | string | Required | Unique key for stable rendering |
label | string | Required | Action label shown to users |
icon | ReactNode | Required | Icon rendered in a fixed 16px slot |
onClick | () => void | Promise<void> | - | Click handler for the action |
disabled | boolean | false | Disables interaction and applies disabled styling |
loading | boolean | false | Shows spinner, disables interaction, sets busy UI |
variant | "default" | "destructive" | "default" | Visual variant; use "destructive" for dangerous actions like delete |
Behavior
- Rows are rendered as
<button type="button">elements. disabledorloadingmakes the row non-interactive.- When
loadingistrue, the icon slot renders a spinner without shifting layout. - Empty actions show
emptyContent, which defaults to a styled message localized using the AppShell locale:No actions availablein English or利用可能なアクションはありませんin Japanese.
Custom Empty Content
Pass a string or any ReactNode to emptyContent to replace the default content when actions is empty. Strings use the same muted Text body styling as the default message. Other React nodes render directly inside Card.Content without a paragraph wrapper and control their own typography. Card.Content owns the spacing, and the title, empty content, and action icons share the same 32px horizontal inset. Action rows retain horizontal padding around their hover backgrounds and focus rings. Custom empty content is ignored when actions are present.
tsx
function CustomEmptyContent() {
return (
<ActionPanel
title="操作"
actions={[]}
emptyContent={
<div>
<p>利用可能な操作はありません</p>
<p>ステータスが変わると、操作が表示されます。</p>
</div>
}
/>
);
}Pass null to hide the empty content and its additional vertical spacing while keeping the card and its title. To hide the entire panel when no actions are available, render it conditionally with actions.length > 0.
With Loading State
Use parent state to control which row is loading:
tsx
const [loadingKey, setLoadingKey] = useState<string | null>(null);
const handleConfirm = async () => {
setLoadingKey("confirm");
try {
await confirmOrder(orderId);
} finally {
setLoadingKey(null);
}
};
<ActionPanel
title="Actions"
actions={[
{
key: "confirm",
label: "Confirm order",
icon: <CheckIcon />,
onClick: handleConfirm,
loading: loadingKey === "confirm",
},
]}
/>;CommandPalette Integration
ActionPanel automatically registers its enabled actions to the CommandPalette. Actions that are not disabled, not loading, and have an onClick handler are registered under the panel's title as the group name. Registration is cleaned up when the component unmounts.
This means users can discover and trigger any visible ActionPanel action via Cmd+K / Ctrl+K without any additional setup.
tsx
// Actions in this panel will automatically appear in the CommandPalette
<ActionPanel
title="Order Actions"
actions={[
{ key: "confirm", label: "Confirm order", icon: <CheckIcon />, onClick: handleConfirm },
{ key: "cancel", label: "Cancel order", icon: <XIcon />, onClick: handleCancel },
]}
/>Note:
ActionPanelmust be rendered insideAppShell(which provides the requiredCommandPaletteProvider). Rendering it outside that tree will throw.
Backend-Driven Pattern
In ERP apps, the backend often decides which actions are available for a document. A common setup:
- Backend returns document data and available action keys.
- Frontend maps each key to a registry entry (
label,icon,execute). - Page builds
ActionItem[]and wiresonClick. - Page controls
loadingper key and passes it toActionPanel.
This keeps ActionPanel simple and reusable across entity types.
Accessibility
- Action list uses
role="list"and each row wrapper usesrole="listitem". - Buttons are keyboard accessible and include focus-visible states.
- Loading rows set
aria-busy="true". - Disabled rows set
aria-disabled="true"and nativedisabled.
Styling
The component is styled with the library's internal astw:-prefixed utilities:
- Full width card
- Compact spacing for dense action lists
- Fixed icon slot for stable alignment
- Inline spinner for loading state
Use className on ActionPanel for card-level properties it does not set itself. Write plain utilities — see Styling AppShell components.
Examples in This Repo
examples/app-moduleincludes an Action Panel Demo resource.- The 2 Columns layout page also includes an ActionPanel with loading behavior.
Related Components
- Layout - Compose page-level column layouts
- DescriptionCard - Display structured record fields
- Badge - Show statuses and semantic labels
Related Concepts
- Modules and Resources - Organize pages and routes
- Routing and Navigation - Navigation patterns