f-ui
Design

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)

Rankshadcn variantWhen
Highestdefault (solid)The one recommended complete action in that group — Submit, Save, Create
Midoutline / secondaryImportant but not the lifecycle primary — Download on a card, Cancel beside Submit
Lowghost / linkInspect / light tools — Preview, Refresh, in-table text actions
RiskdestructiveIrreversible or harmful — Delete, Void, Revoke
Specialdashed outlineAdd-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

PatternWhenMust
Label onlyBusiness-specific or ambiguous verbs
Icon + labelUniversal metaphors on card / section headersIcon left of label
Icon onlyDense toolbars, Attachment/table rows, column toolsTooltip (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

ToneButtonsPrefer instead
NeutralAlmost all productive actions
Solid primaryLifecycle primary only
DestructiveDelete / revoke familyConfirm dialog
Success green on a buttonAvoidStatusTag (e.g. Applied)
WarningCaution confirm flowsNot 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):

SurfaceDownloadPreview
Card headeroutline + icon + Downloadghost + icon + Preview
Documents / Attachment rowIcon-only + Tooltip “Download”File name opens Preview (not a second outline Download)
Solid primary / destructiveNever 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

On this page