# UI Handbook This handbook defines how business pages in the modern ERP app should use Element Plus through the local ERP wrapper layer. It is the practical companion to `DESIGN.md`. ## Wrapper-First Rule - Start with `src/components/erp` before composing raw Element Plus controls. - Use wrappers for shared behavior, state handling, density, labels, and administrator-facing copy. - Use local page CSS only for page-specific grid placement or one-off domain composition. - Do not scatter colors, spacing, radius, table density, or state styles across business pages. - When two pages need the same arrangement, promote it into an ERP wrapper or a class in `src/styles/erp-patterns.css`. ## Style and Token Ownership - `src/styles/erp-ui.css` is the style entry and imports the ERP design system files. - `src/styles/tokens.css` owns color, spacing, radius, typography, control height, table height, card density, and shadows. - `src/styles/base.css` owns global element defaults such as box sizing, body typography, and base surface color. - `src/styles/element-overrides.css` maps Element Plus variables and base component styling to ERP tokens. - `src/styles/erp-patterns.css` owns reusable ERP classes such as forms, panels, cards, metrics, tables, status lines, menus, breadcrumbs, drawers, upload, empty, skeleton, and detail descriptions. - `src/styles/modern.css` owns the application shell, top menu, left menu, route layout, and existing domain-specific layout patterns, and is imported directly by `src/main.ts`. Use tokens such as `--erp-space-3`, `--erp-control-height`, `--erp-table-row-height`, `--erp-radius-xs`, `--erp-color-border-soft`, and `--erp-color-surface-quiet` instead of hard-coded values. ## Page Structure Use this order for business pages: 1. `ErpPageHeader` for module position, domain title, short operational description, and the one primary command for the page. 2. Search or filter area with `ErpSearchForm` when fields come from metadata, or wrapper-owned compact controls when the page only has one or two fixed filters. 3. State summary using metric cards, queue rows, alerts, or `ErpStatusTag` when the state changes what the administrator should inspect next. 4. Main data area with `ErpDataTable` for record lists, remote entity rows, or route-owned rows. 5. Detail entry through the table drawer or `ErpDrawer`; keep long record facts out of inline expanded rows. 6. Secondary executable actions through `ErpActionBar`; use a right-side panel only when actions need supporting context. For common domain pages, prefer `ErpDomainAdminView` because it already combines header, search, metrics, queue, table, side panel, and detail drawers. ## Component Choice Matrix | Need | Use | Notes | | --- | --- | --- | | Application shell, left menu, top menu | `ErpAppShell` | Do not duplicate global navigation inside pages. | | Page title and breadcrumbs | `ErpPageHeader` | Use `crumbs` and the actions slot for page-level commands. | | Domain management page | `ErpDomainAdminView` | Best default for order, product, party, accounting, facility, operations, content, marketing, HR, and similar workspaces. | | Data listing | `ErpDataTable` | Includes loading, empty, unavailable, error, pagination, sorting, status cells, and detail drawer. | | Search filters | `ErpSearchForm` | Metadata-driven fields, lookup, option loading, reset and submit. | | Entity create or edit form | `ErpEntityForm` | Use for submit-oriented fields and disabled state handling. | | Status label | `ErpStatusTag` | Use for row status, queue state, and record detail status. | | Action group | `ErpActionBar` | Use for executable actions, disabled reasons, running state, and overflow. | | Status-grouped data panel | `ErpTabbedDataPanel` | Use when one data panel needs stable tabs, header actions, and table or queue content. | | Record detail | `ErpDrawer` or table drawer | Prefer drawer over expanding large inline detail blocks. | | File selection | `ErpUpload` or upload field in `ErpEntityForm` | Keep upload copy and permission behavior consistent. | | Lookup field | `ErpLookup` through form wrappers | Use for party, product, order, and related entity selection. | ## Tabbed Data Panels - Use `ErpTabbedDataPanel` when one work area contains the same kind of records split by operational state, queue, or ownership. - Good fits: sales orders by fulfillment state, procurement requirements by approval state, inventory exceptions by facility, accounting documents by close state, content items by publish state, and work requests by assignment state. - Keep tabs stable across refreshes. Do not create tabs from every transient filter value; use filters inside the active tab for dates, owner, keyword, and related entity. - Put `ErpDataTable`, a compact queue list, or a focused review panel in the default slot. Search, pagination, and row details stay owned by that inner content. - Use `count` when each tab represents a queue size; use `status` when the tab needs a short state label such as Ready, Review, or Processing. - Use `dense` for secondary panels inside a wider workspace. Leave normal density for the main record panel. - Use the actions slot for panel-level refresh, assignment, export, create, or batch commands that apply to the active tab. If the action applies to a selected row, keep it in the table row or detail drawer instead. - Do not use it for whole-page route navigation, decorative tab stacks, or unrelated datasets that do not share columns or workflow. ## Tables - Use `ErpDataTable` for business records: orders, invoices, payments, requirements, shipments, inventory items, parties, work efforts, requests, content records, system jobs, and entity-backed rows. - Use the `rows` prop when the page already owns a filtered list. Use `dataSource` when the table should load an entity list and keep the source tag, search, refresh, pagination, and unavailable state together. - The wrapper displays up to 12 visible data columns and then the fixed action column. If a workflow needs more facts, move secondary facts into the drawer or a support panel instead of widening the table. - Put status fields through `ErpStatusTag`; `ErpDataTable` already detects status-like column names for standard cells. - Keep row details in the table drawer unless the page has a domain-specific drawer with stronger record context. - Use pagination for remote, large, or repeatedly refreshed record sets. Default scan size is 20 rows; 10 is suitable for narrow panels, 50 or 100 for audit-heavy pages. - Keep table search in the source toolbar for broad keyword search. Use `ErpSearchForm` above the table for structured filters such as party, product, facility, status, date range, and owner. - Preserve built-in loading, empty, unavailable, and error states. Do not replace the whole page when only one table source fails. - Use `.erp-table` and `.erp-dense-pagination` only when a wrapper cannot be used, such as a route renderer that supplies specialized table slots. Table layout rules: - Primary identifier or name comes first. - Status and date columns stay visible when possible. - Amount columns align right. - Action columns stay narrow and predictable. - Long secondary values use muted subtext and ellipsis. Table density rules: - Main work tables use compact small rows from `ErpDataTable`; this is the default ERP density. - Dense side tables should show 5-8 rows and avoid multi-line cells. - Audit or exception tables may show 20-50 rows, but must keep identifier, state, timestamp, owner, and action columns visible. - Do not increase row height to carry descriptions. Put descriptions in the drawer, a tooltip, or muted subtext under the primary value. - Keep toolbar, total count, and pagination visible near the table so operators can tell whether they are seeing a filtered subset. ## Search Forms - Use `ErpSearchForm` when query fields come from screen metadata, entity definitions, adapter blocks, or an action contract. - Use direct compact controls only for one or two page-local switches such as status, facility, or active tab. Once the page has three or more structured fields, use `ErpSearchForm`. - Place search forms above the table or inside the domain wrapper's control area. Do not place a full search form inside a table body. - The wrapper shows up to 16 visible fields. Put the most operational fields first: identifier, status, related party/product/facility, date range, owner, and free text. - Use `ErpLookup` for related records rather than free text when the domain has a known entity. - Keep top labels and compact controls. Avoid placeholder-only forms; labels must remain visible. - Preserve reset and submit behavior. The action row also carries form status such as queryable, submittable, login required, readonly, or processing. - Put rarely used filters behind a drawer or collapsible panel, but keep the current filter state visible near the table after applying it. Search form layout: - Desktop: four compact columns when space allows. - Medium width: two columns. - Narrow width and drawers: one column. - Actions align to the end using `.erp-form-actions`. ## Entity Forms - Use `ErpEntityForm` for create, edit, and submit-oriented flows. - Use one column inside drawers and dialogs. - Use two columns in wider page panels only when fields are short and related. - Show readonly values with wrapper readonly display, not disabled inputs unless the value belongs in a form control. - Show disabled reasons near the form or action area. - Keep submit actions in a stable bottom action row. ## Navigation - `ErpAppShell` owns top-level navigation and the left menu. - Business pages should receive navigation context rather than rebuilding it. - Use `ErpPageHeader` breadcrumbs for page-level location. - Use `ErpPageHeader` on every route-level admin page, including domain workspaces, entity search pages, and operational consoles. - Header actions are for page-level commands such as create, refresh, export, or open a global action. Row-specific commands stay in `ErpDataTable`, the drawer, or `ErpActionBar`. - Keep the page description to one sentence that states the operator's job: which records are managed, which queue is handled, or which state is monitored. - Use `.erp-side-menu` only for page-local secondary navigation. - Use `.erp-horizontal-menu` for top-level tabs or route groups inside the shell. - Top menu items should be short domain labels. - Left menu groups should be stable and scan-friendly. Breadcrumb rules: - First item is the admin workspace or module root. - Middle items are domain or sub-area. - Last item is the current page or record label. - Breadcrumb text should be business-facing and concise. ## Cards, Panels, and Metrics - Use `.erp-panel` or `.erp-work-area` for framed work sections. - Use `.erp-card` only for individual repeated items, not for whole page sections. - Use `.erp-metric-card` for numeric summaries. - Use density variants deliberately: - Large: first-screen domain summaries with a number, short label, and one operational hint, such as open orders, exceptions, value at risk, or jobs running. - Medium: normal panel item, queue card, or record group that carries a title, one secondary line, and one status or action. - Small: row summary, action candidate, handoff, warning, or compact queue item in a side panel. - Mini: numeric counters and short KPI chips inside a header or compact workspace. - Avoid nested cards. - Use neutral surfaces; state color belongs on a side border, tag, alert, or icon. - A card must help an operator compare, choose, or act. If it only wraps text, use a heading, list row, or panel section instead. - Large cards should be rare on record-heavy pages; use them for the top 3-4 domain measures, then return to tables. - Medium and small cards may repeat in grids or side panels. Keep their internal text short enough that the grid height remains stable. - Do not use cards as section containers around `ErpDataTable`, `ErpSearchForm`, or `ErpTabbedDataPanel`; those wrappers already provide the working surface. ## Status, Alerts, and Feedback - Use `ErpStatusTag` for record, queue, tab, and drawer labels when the value is a business state such as Approved, Review, Created, Processing, Error, Cancelled, Ready, Packed, or equivalent domain wording. - Use status tags in table cells, queue rows, selected record headers, and compact metric cards. Avoid placing multiple status tags in the page header unless they describe the whole page. - Keep the source status from the data, then map display labels through the wrapper. Do not invent local color classes for every domain state. - Use `.erp-state-line` for state rows that need short text plus a state color. - Use Element Plus alerts with `.erp-status-alert` for warnings or errors that affect a whole panel. - Pair every color with text. - Place state messages near the affected control or table. State expectations: - Loading: indicate what is loading. - Empty: explain the current filter or missing selection. - Error: name the affected area and keep other controls usable. - Disabled: explain why the action cannot run. - Success: keep feedback brief and tied to the completed action. ## Dialogs and Drawers - Use drawers for record details, edit forms, action setup, and side-by-side review. - Use dialogs for short confirmations or focused blocking decisions. - Drawer titles must identify the record or action. - Drawer content should use one-column forms, descriptions, timelines, or action lists. - Long detail content should be grouped with headings and descriptions. - Keep primary and secondary actions at the bottom or in a stable action row. - Use `.erp-drawer`, `.erp-dialog`, and `.erp-detail-descriptions` classes where applicable. ## Upload - Use `ErpUpload` or the upload field support inside `ErpEntityForm`. - Keep upload areas compact and connected to the form they affect. - Show file selection state and any permission note close to the upload control. - Do not create custom drag areas with local CSS when the shared upload wrapper is enough. - Use `.erp-upload` for shared upload styling when direct Element Plus upload is unavoidable. ## Actions - Use `ErpActionBar` for executable business actions returned by a route, adapter block, service contract, or selected record context. - Use the page header for one page-level primary command. Use `ErpActionBar` when there are multiple executable choices, permission-dependent actions, batch commands, or a need to show why an action cannot run. - Primary action appears first and uses primary styling. Destructive actions use danger styling and clear labels. - Normal mode shows up to four primary buttons before overflow. Compact mode shows up to two; use compact mode in side panels, drawers, and secondary consoles. - The wrapper shows enabled count, disabled reason, running state, success, warning, and error feedback. Do not duplicate that feedback with page-local banners unless the whole panel is affected. - Disabled actions must expose a reason through the action definition. A disabled button without a reason is not acceptable for administrator workflows. - Running actions show button loading and should not shift layout. - Use `.erp-action-stack` and `.erp-action-button` for custom action groups that cannot use `ErpActionBar`. ## Top Menu and Left Menu - Top menu: - Use shell-owned top navigation. - Keep labels short and domain-oriented. - Active state uses the ERP primary token and a restrained underline. - Left menu: - Use shell-owned left navigation for modules and durable route groups. - Use `.erp-side-menu` only for local subnavigation. - Keep item height aligned with the 38px menu rhythm. - Active state uses primary color and a left border, not a filled pill. ## Empty, Loading, and Error States - Empty states use Element Plus empty components or `.erp-empty-state`. - Loading states use wrapper loading behavior, Element Plus loading, or `.erp-skeleton`. - Error states should not replace the whole page unless the whole page is unusable. - For tables, keep the toolbar and search visible during failures. - For forms, preserve entered values when an option load or submit action fails. ## Responsive Layout - Use CSS grid and wrappers that can collapse cleanly. - Search forms should move from four columns to two and then one. - Metrics should wrap instead of shrinking text below readable size. - Side panels move below the main table on narrow widths. - Drawers keep one-column content and readable labels. - Do not scale font size with viewport width. ## Accessibility and Interaction - Keep visible focus behavior from Element Plus and token overrides. - Do not remove native labels from forms or headers from tables. - Icon-only buttons require accessible labels or tooltips. - Use buttons for actions and links for navigation. - Keep hover states helpful but nonessential. - Do not communicate status by color alone. ## Content Rules - Write for administrators: short, operational, and specific. - Use verbs for buttons: search, reset, refresh, create, save, assign, approve, cancel. - Use nouns for sections: filters, records, details, actions, queue, status. - Avoid visible implementation wording. - Avoid promising behavior that the page cannot currently perform. ## Local CSS Rules - Business pages may add local classes for layout only when no wrapper or pattern exists. - Local CSS should reference ERP tokens and stay narrowly scoped. - Do not redefine Element Plus button, input, table, tag, menu, or card styles inside a page. - Do not add hard-coded state colors in pages. - If a layout pattern appears in multiple pages, move it into `src/styles/erp-patterns.css` or an ERP wrapper. ## Review Checklist - Does the page use the right ERP wrapper for shell, header, table, form, status, actions, detail, and upload? - Are tokens used instead of hard-coded color, spacing, radius, and table density? - Are search, data table, state labels, action area, detail entry, empty, loading, and error states visible? - Does the page avoid decorative card stacks and local style drift? - Is copy business-facing and useful to an administrator? - Does the page work at desktop, medium, and narrow widths?