Skip to Content
UI playbookChọn component theo việc

Chọn component theo việc

Decide from the job on the screen, not from the data. One line per job: component · props that make it right · the mistake to avoid.

Pages & navigation

  • App frame, menu per role → ProLayout (layout="mix", route.routes filtered by role, menuItemRender → router, avatarProps + actionsRender) · not a hand-made Layout.Sider.
  • Page title + actions + tabs → PageContainer (title, subTitle, extra=[buttons], tabList/onTabChange, content for a summary line) · not <h1> + <Space>.
  • Long form/detail with a sticky save bar → FooterToolbar (extra = total/status, children = buttons).

Work queues (“what do I need to do”)

  • Records to process by status → ProTable with toolbar.menu tabs + Badge counts (type: "tab"), request + params={{ tab }}, row actions as valueType: "option", rowSelection + tableAlertOptionRender for bulk approve · not Table + your own fetch.
  • Items moving through stages (production, pipeline, tickets) → board: ProCard split="vertical" (or Row/Col) with one column per status, each a ProList/Card stack, status change by a button/Dropdown on the card (no drag library in the allowlist) · not a table when the user thinks in columns.
  • My inbox / approvals → ProList (metas: title, description, subTitle Tag, actions, extra) with toolbar.menu tabs · good for 1–3 actions per item.
  • Today’s tasks for one role → ProCard grid of small ProLists (“Cần giao hôm nay”, “Hàng sắp về”…) on one page.

Lists of records

  • Searchable list → ProTable (search={{ labelWidth: "auto" }} built from columns, valueEnum for status/select, valueType: "money" | "percent" | "dateTime" | "dateRange", hideInSearch/hideInTable, fieldProps), pagination.showTotal, options={{ density: true, setting: false }} · server returns { data, total }.
  • Few filters, compact → toolbar.filter with LightFilter · many → ProTable search or QueryFilter on a non-table page.
  • Ordered rules / priorities the user re-orders → DragSortTable (dragSortKey, onDragSortEnd).
  • Gallery / product cards → ProList grid={{ gutter: 16, column: 3 }} or Card + Image · CheckCard.Group when the user picks one/many of them.
  • Tree data (categories, BOM levels, org) → Table/ProTable with children rows, or Tree/TreeSelect for picking.

Entering data

  • Create/edit a record from a list → DrawerForm (≥ 6 fields or line items) / ModalForm (≤ 5 fields), trigger = the button, onFinish returns true to close, initialValues for edit · not a separate page.
  • Document with line items (order, purchase, invoice, BOM) → EditableProTable inside the form (recordCreatorProps, editable.type="multiple", money columns read-only and computed) or ProFormList for short repeated groups; show the server-computed total in FooterToolbar/ProDescriptions after save · never let the client send prices or totals.
  • Field depends on another (customer → address, product → unit price shown) → ProFormDependency / Form.useWatch; options from the API with ProFormSelect request + showSearch (debounceTime).
  • Money / quantity / percent → ProFormMoney (locale="vi-VN"), ProFormDigit (min, fieldProps.precision), ProFormSlider/ProFormDigit + %.
  • Dates → ProFormDatePicker / ProFormDateRangePicker with transform to the API format (never format alone — gate code.proform-date-format-submit).
  • Multi-step setup / onboarding → StepsForm (StepsForm.StepForm per step, onFinish on the last).
  • Same fields reused in table + form + detail → one columns array + BetaSchemaForm (layoutType="DrawerForm"|"ModalForm"|"Form").
  • Confirm a risky action → Popconfirm (one click) · with a reason → ModalForm with a required ProFormTextArea · irreversible bulk → Modal.confirm stating counts and money.
  • Files / photos → Upload (listType="picture-card" for photos, beforeUpload to check type/size, customRequest → your API or a presignPut URL), show with Image (preview).

Reading a record

  • Record header → PageContainer title + status Tag + extra actions allowed in this status for this role (hide, don’t disable, what the role can never do).
  • Fields → ProDescriptions (column={{ xs: 1, md: 2, xl: 3 }}, same valueTypes as the table, dataSource from the detail call).
  • Where it is in its lifecycle → Steps (current from status, status="error" when cancelled) · history → Timeline (who · what · when, newest first).
  • Related lists (lines, payments, movements) → ProTable with search={false}, toolBarRender={false}, pagination={false}, summary row for totals.
  • Printable document (delivery note, invoice) → plain Descriptions + Table (bordered, size="small") on a page with a “In” button calling window.print().

Numbers & dashboards

  • KPI row → StatisticCard.Group (statistic.value, valueType, description with trend up/down vs last period, chart mini) · one KPI per card, max 4–6.
  • Trend over time → Line/Area · compare categories → Column/Bar · share of a whole (≤ 6 parts) → Pie with innerRadius · target vs actual → Progress/Gauge/Liquid · two measures, two scales → DualAxes · stage drop-off → Funnel (all from @ant-design/charts; set height, autoFit, axis label formatter for money).
  • Ranked list (top customers, late orders) → ProList/Table size small with Progress or Statistic per row.
  • Calendar of due dates / bookings → Calendar with cellRender (antd v6 — not dateCellRender) or ProTable grouped by date.

Feedback & states

  • Loading → Skeleton/table loading; empty → Empty with one sentence + the next action button; error / no permission / done → Result (status="403" | "404" | "success" | "warning").
  • Short success/error after an action → App.useApp().message; long-running or cross-page → notification; server 4xx text shown as-is.
  • Status everywhere → Badge status / Tag color from one valueEnum map; overdue/short → Typography.Text type="danger".
  • Guided first run → Tour · watermark on exported/printed pages → Watermark.

AI features (only if the product asks for them) → @ant-design/x: chat = Bubble.List + Sender, suggestions = Prompts, conversations = Conversations; backend via app/std/openai.

Last updated on