UI/UX Enterprise Standards
Status: Approved — canonical for platform console UI work
Last updated: 2026-06-10
Applies to: TenancyEngine.Console, TenaBill.Web console, VectraLabel app, future platform consoles
Principles
- Mobile-first — layouts must work on narrow viewports without trapping users in centered overlays.
- Enterprise-grade editing — multi-field entities use dedicated surfaces (pages, side panels, inline expansion), not popup CRUD.
- Feature surface matches domain size — large configuration domains get their own nav section and sub-routes.
- Table row actions navigate — primary create/edit flows go to a route; they do not open a modal by default.
Popup / modal policy
Default: NO modals for create, edit, or view flows
Do not use Modal, Dialog, or centered popups for:
- Creating or editing records with more than one field
- Viewing entity detail that users may bookmark or share
- Configuration wizards for offerings, plans, entitlements, billing, templates, branding, API keys, team roles, or application settings
Allowed uses (narrow exceptions)
| Use | Pattern |
|---|---|
| Destructive confirmation | Small confirm dialog (delete, revoke, disable webhook) — prefer typed confirm for high-risk actions |
| Single-field micro-edit | One inline field or toggle with immediate save |
| Mobile pickers | Bottom sheet or drawer — not a centered modal |
| Impersonation / security gate | Short confirmation with context (who, why, duration) |
Mobile-first overlays
On viewports < 768px:
- Prefer full-page forms or slide-over panels (master-detail)
- Use bottom sheets for pickers and short confirmations
- Avoid fixed-width centered modals that clip content or trap scroll
Edit patterns (enterprise)
| Complexity | Pattern | Example |
|---|---|---|
| Simple scalar | Inline edit / click-to-edit | Toggle enabled, rename slug with validation |
| Few fields on list context | Inline expansion panel (accordion row) | Quick status change on a table row |
| Multi-field entity | Detail page or side panel (master-detail) | Tenant settings, webhook config, API key metadata |
| Genuinely multi-step | Wizard / stepper only when steps are real | Signup, onboarding, first-time merchant setup |
| Destructive | Confirm dialog (not a full edit modal) | Revoke key, delete user, disable webhook |
Anti-pattern: Stacking multiple modals on one page for features, roles, claims, offerings, environments — each should be a tab section with inline or routed edit.
Feature surface area rules
Large domains get own nav section + sub-pages (never modal CRUD):
| Domain | Expected structure |
|---|---|
| Offerings / platform plans / entitlements | List → plan/offering detail → tabs (features, pricing, entitlements, marketing) |
| Billing & subscriptions | Billing overview → plan change (inline or dedicated step), invoices as sub-routes |
| Email templates & notifications | Category list → full-page editor (already correct pattern) |
| Support / communications (future) | Ticket list → ticket detail thread |
| Custom domains / branding | Settings section → domain wizard page, branding preview page |
| API keys, webhooks | List page → create on /new or side panel; revoke = confirm only |
| Team & roles / permissions | Team list → member detail / invite page; roles as admin sub-section |
| Application configuration | App list → app detail page with tabs; sub-entities as tab sections or nested routes |
Standard flow
List page → Detail page → Configuration tabs / sections
↑ ↑
"Add" navigates Row click navigates (not modal)Table row primary actions: navigate to /console/.../:id or /console/.../:id/edit, not setShowModal(true).
Console patterns to follow (good)
| Pattern | Location | Why |
|---|---|---|
| Platform plans list + cards | TenancyEngine.Console/src/pages/PlatformPlansPage.tsx | Read-only grid, SectionCard, links outward — no modal CRUD |
| Platform email templates | TenancyEngine.Console/src/pages/PlatformEmailTemplatesPage.tsx | Category via query param, full-page editor, preview inline |
| TenaBill email templates | TenaBill.Web/src/pages/console/EmailTemplatesPage.tsx | Same category + editor model (one small modal for send-test only) |
| App detail tabs (structure) | TenancyEngine.Console/src/pages/ApplicationDetailPage.tsx | Tabbed detail layout is correct — modals inside tabs are the problem |
| Destructive confirm only | TenaBill.Web/src/pages/console/ApiKeysPage.tsx | Revoke uses modal for confirm, not for create/edit |
| Plan change confirm | TenaBill.Web/src/pages/console/BillingPage.tsx | Confirmation before mutation — acceptable |
Shared form UX (all consoles): @platform/form-ui — FriendlyFormError, EmailField + signInHref, debounced field checks (~450ms), password strength aligned with Identity options.
Anti-patterns in current codebase (refactor candidates)
Grep target: Modal, Dialog, popup in console src/.
TenancyEngine.Console — top modal-heavy pages
| Rank | File | Modal count (approx.) | Issue |
|---|---|---|---|
| 1 | src/pages/ApplicationDetailPage.tsx | ~11 | Features, roles, claims, offerings, environments, destructive actions — all modal CRUD on a detail page |
| 2 | src/pages/TenantsPage.tsx | 3 | Tenant create/edit in modals; should be detail routes or expansion |
| 3 | src/pages/ApplicationsPage.tsx | 3 | Application create/edit in modals |
| 4 | src/components/AppTenantsPanel.tsx | 3 | Tenant linkage modals on app context |
| 5 | src/pages/OrganizationsPage.tsx | 3 | Org create/edit modals |
Also flagged: UsersPage.tsx (2), TenantUsersTable.tsx + ImpersonateUserModal.tsx (impersonation confirm — keep narrow), ApiKeysPage.tsx (1 confirm — OK).
TenaBill.Web console — modal usage
TB is mostly aligned: confirmations on ApiKeys, Webhooks, Billing. Exception:
| Rank | File | Issue |
|---|---|---|
| 1 | src/pages/console/CatalogPage.tsx | Create/edit catalog items in modals — should be catalog item detail page or side panel |
| 2 | src/pages/console/EmailTemplatesPage.tsx | Send-test modal — acceptable micro-action |
| 3–5 | ApiKeysPage, WebhooksPage, BillingPage | Confirm-only — keep |
Implementation checklist
When implementing or reviewing UI work:
- [ ] Read this document before UI work
- [ ] No new modals for large CRUD unless explicitly tagged
micro-editorconfirm-destructivein the task - [ ] Large features = dedicated routes — offerings, plans, entitlements, templates, branding, webhooks, team, app config
- [ ] List → detail → tabs for multi-field entities
- [ ] Mobile check — no centered modal for primary flows; use drawer/sheet or full page
- [ ] Table actions navigate, not open modal
- [ ] PermissionGate /
platformPermissions/consolePermissionson gated actions - [ ] Loading + error states on every async surface
- [ ] Tests in same change as feature — unit (+ integration for HTTP)
Reviewer auto-REJECT
Reviewers must reject when:
- New
Modal/Dialogfor multi-field create/edit on large domains - Mutating API without permission checks (
RequirePermissionAsync,PlatformPermissionChecker, TB console guards) - New public logic with no tests for non-trivial behavior
- Incomplete feature (half UI, no API, or vice versa when task required both)
- Breaks existing E2E/console patterns or repo conventions
- Automated verification gate failed (
dotnet test/npm run build)
Related docs
- Identity, MFA & notifications — identity and notification UX
- Local development — running consoles locally
@platform/form-ui— shared form components (FriendlyFormError,EmailField, debounced field checks)