frontend/portal/UI_CONVENTIONS.md
The portal has two component sources. The rule:
Simple, presentational, brand-defining UI → our SUI design system (
@shared/components). Complex, stateful, or accessibility-hard widgets → Mantine. Don't reinvent what Mantine already does well; do own the look of the simple, high-frequency pieces.
Both are theme-bound: MantineProvider in App.tsx is wired to the portal's
ThemeProvider (mantineTheme.ts maps the brand palette), so Mantine widgets
follow the same light/dark switch and brand colours as SUI. The provider is
intentional — it exists precisely so we can drop Mantine widgets in where they
earn their keep.
@shared/components) — our own styleLayout and presentational primitives we want full brand control over and that are cheap to own:
Button · Card · StatusBadge / MethodBadge · Chip · MetricCard ·
MetricStrip · StatTile · ProgressBar · Avatar · Banner · Skeleton ·
Spinner · EmptyState · NavItem · PanelHeader · SectionDivider ·
Stack / Inline · Table (static/presentational) · CodeBlock ·
FormField (label/help/error layout) · simple Tabs.
Anything that needs portals, focus traps, ARIA keyboard patterns, or is just a solved hard problem:
Modal, Drawer, Popover (focus trap, scroll lock, escape, focus restore)Menu (roving arrow-key navigation)Select / MultiSelect / Combobox / Autocomplete (keyboard + filtering)@mantine/dates DatePicker / DatePickerInput (e.g. billing period range)@mantine/dropzone Dropzone (connect-source upload, op-runner sample drop)Stepper (multi-step wizards), Notifications, Tooltip@mantine/hooks (useDisclosure, useClickOutside, useHotkeys, …) over hand-rolling.Mantine is mature and battle-tested for accessibility. A review of the
hand-rolled SUI overlays found real gaps — Dropdown has no arrow-key
navigation, Modal/Drawer mishandle focus when there are no focusable
children, Toast uses role="alert" for every tone — exactly the things
Mantine gets right. Owning those is wasted effort and an a11y liability.
These shipped as SUI primitives during the initial build and should move to Mantine equivalents (fixes the a11y findings above):
| Today (SUI) | → Mantine |
|---|---|
Dropdown (menus: tier switcher, app switcher, notifications) | Menu |
Modal (composer, wizards, settings, create-key) | Modal |
Drawer (pipeline detail) | Drawer |
Toast | notifications |
| new need: billing date range | @mantine/dates |
| new need: file upload | @mantine/dropzone |
Keep Tabs SUI for the simple in-page switchers; only reach for more if a true
tabpanel/roving-focus contract is needed.
Migrating overlays touches visible chrome and behaviour, so do it deliberately (with eyes on the result), not as a blind sweep.