Button And Action Emphasis
Global locked rules for every in-app button — primary vs outline vs ghost, icons vs icon-only, destructive and warning — Ant-backed, mapped to shadcn Button.
This page is global — it applies to every in-app action control (page headers, form footers, Object Page cards, table/region toolbars, Attachment rows, dialogs, Empty CTAs). A shipping-label Download/Preview pair is one illustration of the same rules, not a special exception.
f-ui follows Ant Design Button principles through shadcn Button variants (neutral identity — not Ant blue).
Examples
Document Utilities
Download and Preview consume an issued file — they are not lifecycle primaries. Left side makes them solid (and paints Refresh destructive). Right side matches the card-header matrix: outline Download, ghost Preview / Refresh.
Wrong
Shipping label
Two solid primaries + destructive on a utility
Right
Shipping label
Document utilities: outline Download, ghost Preview / Refresh
import { Download, Eye, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { DesignCompare } from "@/demos/_design/design-compare";
export function ButtonAndActionEmphasisDemo() {
return (
<DesignCompare
wrong={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Shipping label
</p>
<div className="flex flex-wrap items-center gap-2">
<Button>
<Download className="size-4" />
Download
</Button>
<Button>
<Eye className="size-4" />
Preview
</Button>
<Button variant="destructive">
<RefreshCw className="size-4" />
Refresh
</Button>
</div>
<p className="text-xs text-muted-foreground">
Two solid primaries + destructive on a utility
</p>
</div>
}
right={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Shipping label
</p>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline">
<Download className="size-4" />
Download
</Button>
<Button variant="ghost">
<Eye className="size-4" />
Preview
</Button>
<Button variant="ghost">
<RefreshCw className="size-4" />
Refresh
</Button>
</div>
<p className="text-xs text-muted-foreground">
Document utilities: outline Download, ghost Preview / Refresh
</p>
</div>
}
/>
);
}One Solid Primary Per Group
A form footer may have several actions, but only one solid primary. Left side stacks three solids; right side keeps Submit as the only solid.
Wrong
Form footer
Three solid primaries in one group
Right
Form footer
≤1 solid primary; draft and cancel stay quieter
import { Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import { DesignCompare } from "@/demos/_design/design-compare";
export function ButtonEmphasisPrimaryGroupDemo() {
return (
<DesignCompare
wrong={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Form footer
</p>
<div className="flex flex-wrap items-center gap-2">
<Button>Save</Button>
<Button>Submit</Button>
<Button>
<Download className="size-4" />
Download
</Button>
</div>
<p className="text-xs text-muted-foreground">
Three solid primaries in one group
</p>
</div>
}
right={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Form footer
</p>
<div className="flex flex-wrap items-center gap-2">
<Button>Submit</Button>
<Button variant="outline">
Save draft
</Button>
<Button variant="ghost">
Cancel
</Button>
</div>
<p className="text-xs text-muted-foreground">
≤1 solid primary; draft and cancel stay quieter
</p>
</div>
}
/>
);
}Icon-Only Plus Tooltip
Dense toolbars and Attachment rows may use icon-only buttons — but only with a Tooltip (and aria-label). Left side ships bare icons; right side wraps each control.
Wrong
Line tools
Icon-only cluster with no Tooltip (aria alone is not enough UX)
Right
Line tools
Dense icon-only tools always carry Tooltip (+ aria-label)
import { Download, Eye, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { DesignCompare } from "@/demos/_design/design-compare";
export function ButtonEmphasisIconTooltipDemo() {
return (
<TooltipProvider>
<DesignCompare
wrong={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Line tools
</p>
<div className="flex flex-wrap items-center gap-1">
<Button size="icon" variant="ghost" aria-label="Download">
<Download className="size-4" />
</Button>
<Button size="icon" variant="ghost" aria-label="Preview">
<Eye className="size-4" />
</Button>
<Button size="icon" variant="ghost" aria-label="Refresh">
<RefreshCw className="size-4" />
</Button>
</div>
<p className="text-xs text-muted-foreground">
Icon-only cluster with no Tooltip (aria alone is not enough UX)
</p>
</div>
}
right={
<div className="space-y-3">
<p className="text-sm font-medium text-muted-foreground">
Line tools
</p>
<div className="flex flex-wrap items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost" aria-label="Download">
<Download className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Download</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost" aria-label="Preview">
<Eye className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Preview</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost" aria-label="Refresh">
<RefreshCw className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Refresh</TooltipContent>
</Tooltip>
</div>
<p className="text-xs text-muted-foreground">
Dense icon-only tools always carry Tooltip (+ aria-label)
</p>
</div>
}
/>
</TooltipProvider>
);
}Card Vs Row Document Actions
Do not stack two equally heavy outline Downloads on one Object Page. Left side duplicates outline Download on the card and the file row; right side keeps card outline + ghost Preview, and icon-only Download on the row (file name opens Preview).
Wrong
Shipping label
label-2026-08.pdf
Two equally heavy outline Downloads on one Object Page
Right
Shipping label
Card: outline Download + ghost Preview; row: icon-only Download
import { Download, Eye } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { DesignCompare } from "@/demos/_design/design-compare";
export function ButtonEmphasisDocumentUtilityDemo() {
return (
<TooltipProvider>
<DesignCompare
wrong={
<div className="space-y-3">
<div className="space-y-2 rounded-lg border p-3">
<div className="flex items-center justify-between gap-2">
<p className="text-sm font-medium">Shipping label</p>
<div className="flex items-center gap-2">
<Button variant="outline">
<Download className="size-4" />
Download
</Button>
<Button variant="outline">
<Eye className="size-4" />
Preview
</Button>
</div>
</div>
<div className="flex items-center justify-between gap-2 border-t pt-2">
<p className="truncate text-sm">label-2026-08.pdf</p>
<Button variant="outline">
<Download className="size-4" />
Download
</Button>
</div>
</div>
<p className="text-xs text-muted-foreground">
Two equally heavy outline Downloads on one Object Page
</p>
</div>
}
right={
<div className="space-y-3">
<div className="space-y-2 rounded-lg border p-3">
<div className="flex items-center justify-between gap-2">
<p className="text-sm font-medium">Shipping label</p>
<div className="flex items-center gap-2">
<Button variant="outline">
<Download className="size-4" />
Download
</Button>
<Button variant="ghost">
<Eye className="size-4" />
Preview
</Button>
</div>
</div>
<div className="flex items-center justify-between gap-2 border-t pt-2">
<button
type="button"
className="truncate text-sm text-primary underline-offset-4 hover:underline"
>
label-2026-08.pdf
</button>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost" aria-label="Download">
<Download className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Download</TooltipContent>
</Tooltip>
</div>
</div>
<p className="text-xs text-muted-foreground">
Card: outline Download + ghost Preview; row: icon-only Download
</p>
</div>
}
/>
</TooltipProvider>
);
}Emphasis Ladder (≤1 Solid Primary Per Group)
| Rank | shadcn variant | When |
|---|---|---|
| Highest | default (solid) | The one recommended complete action in that group — Submit, Save, Create |
| Mid | outline / secondary | Important but not the lifecycle primary — Download on a card, Cancel beside Submit |
| Low | ghost / link | Inspect / light tools — Preview, Refresh, in-table text actions |
| Risk | destructive | Irreversible or harmful — Delete, Void, Revoke |
| Special | dashed outline | Add-content invitation in an empty area |
Ant Ghost vs shadcn ghost: Ant’s Ghost button is for dark or colored backgrounds. On light Object Page cards, low emphasis is shadcn ghost/link (= Ant Text button) — do not invent a transparent invert style on white cards.
Icons
| Pattern | When | Must |
|---|---|---|
| Label only | Business-specific or ambiguous verbs | — |
| Icon + label | Universal metaphors on card / section headers | Icon left of label |
| Icon only | Dense toolbars, Attachment/table rows, column tools | Tooltip (or aria-label) always |
Universal metaphors (prefer icon+label on cards): download, preview/view, upload, refresh, settings, search, filter, copy.
Do not: two icons in one button; icon-only without accessible name; decorate every verb with a random icon.
SAP vs Ant: Fiori often forbids icon+text in toolbars. f-ui locks Ant for Object Page headers (icon+label OK) and SAP-like density for list/Attachment rows (icon-only + Tooltip).
Semantic Color On Actions
| Tone | Buttons | Prefer instead |
|---|---|---|
| Neutral | Almost all productive actions | — |
| Solid primary | Lifecycle primary only | — |
| Destructive | Delete / revoke family | Confirm dialog |
| Success green on a button | Avoid | StatusTag (e.g. Applied) |
| Warning | Caution confirm flows | Not Download / Preview / Refresh |
Placement Reminders
- Order by importance; weaker actions right/bottom (Ant).
- Page Create / Submit lives in page header or form footer — see CRUD Action Placement.
- Region table tools stay ghost/outline — see Region Table Toolbar.
Document Artifact Actions
When a file is already issued and the operator only consumes it (PDF, label, export):
| Surface | Download | Preview |
|---|---|---|
| Card header | outline + icon + Download | ghost + icon + Preview |
| Documents / Attachment row | Icon-only + Tooltip “Download” | File name opens Preview (not a second outline Download) |
| Solid primary / destructive | Never for these utilities | — |
Do not put two equally heavy outline “Download” buttons stacked on the same Object Page.
Checklist
- At most one solid primary in this action group.
- Light-card low emphasis uses ghost/link (Ant Text), not Ant Ghost-on-white cosplay.
- Icon-only controls have Tooltip / aria-label.
- Destructive only for harmful irreversible work.
- Document utilities follow the card vs row matrix above.
See Also
- Sentence Case
- CRUD Page Patterns — Action Placement
- Ant Design — Button
- Carbon — Button usage
Sentence Case
Use sentence case for in-app titles, labels, and buttons — Title Case is for docs and registry titles only.
Domain Status Vs Derived View
Separate persisted lifecycle status from derived business views, UI chrome, and capability gates — so frontend labels may differ from backend enums without inventing a second source of truth.