erp-ui
The core ERP UI components — the data-heavy building blocks every brick's screens are made of: tables, filters, record pages, timelines. Built on Radix (Radix Themes + primitives) in a Dodil-inspired default theme, and delivered as source a project owns, so a client can change any of it without forking a library.
| Item | What it does |
|---|---|
theme | ErpTheme: Radix Themes, purple accent, slate gray. Rebrand with one prop or the --accent-* variables |
format | Money as exact text — separators inserted, never through a float; exact decimal comparison and addition (sumDecimal, for totals beside rows the API computed); dates |
json | parseJsonLossless / quoteUnsafeIntegers: JSON where a 64-bit id (bp_id) past 2^53 arrives exactly, as a string — JSON.parse turns it into another partner's id |
data-table | Lists by column kind (text, number, money, date, status): exact money sorting, pagination, loading / empty / error states, open a row by click or Enter. cell is the escape hatch |
filter-bar | Controlled search + select filters, Clear |
detail-page | One record: header, status, actions that are shown disabled with the reason (ActionButton, usable anywhere), a toolbar for dialog triggers, sections of fields, and panels other modules add through an extension point |
timeline | What was said and done, newest first, grouped by day |
status-badge | A state as a coloured badge, with brick-specific tones |
states | LoadingState, EmptyState (with the next step), ErrorState (the API's reason, Try again), NotFoundState (the 404 answer) |
form | FormDialog: fields as data, required/shape checks before sending, the API's refusal shown in place with what was typed kept; opened by a trigger or from code |
money-input | An amount typed by a person, kept as exact decimal text ("12,000.5" → "12000.50"); a third decimal is an error, never rounded |
confirm-dialog | Confirm a step that is recorded or cannot be undone; asks for the reason when the API needs one |
toast | Toaster / useToast: confirms a step that worked. A refusal is never a toast — it stays where the person acted |
kanban-board | Records in columns by state; every move has a keyboard path (a "Move to" menu per card), dragging is the mouse shortcut, a column or card can say why a move is refused |
stat | Stat / StatGrid: the numbers a person starts the day from |
app-shell | The frame: the modules' navigation by section, the account menu, light/dark, a skip link; a sidebar at a desk, a menu on a phone |
contract | The module UI contract every brick's UI exports — screens, extension points, extensions — and checkModules(), which refuses route collisions and panels aimed at a point nobody offers |
Use it
The components install as source into a project (shadcn's registry format; no Tailwind needed):
// components.json in the consuming project
{ "registries": { "@erp-ui": "<where public/r is served>/{name}.json" } }
npx shadcn add @erp-ui/data-table # brings @erp-ui/format and @erp-ui/status-badge with it
Wrap the app in <ErpTheme>. Every component draws colour, spacing and radius from Radix tokens only.
theme.css raises Radix's placeholder text to --gray-11 and StatusBadge uses high-contrast text: both
were under 4.5:1 in a real browser (axe on the CRM screens, light and dark, 2026-10-17).
How a product's UI is assembled
Each brick ships its own UI (web/ in the brick repo) exporting a ModuleUI. The composition's shell
assembles them — by default into one bundle served by the composed app (ui: monolith); the same contract
also allows runtime federation, or each brick serving its own UI. A brick never imports another brick's UI:
it links by URL and plugs panels into extension points, the UI's version of a seam.
Develop
npm install
npm run dev # the gallery: every component in the default theme, with CRM data
npm test # 30 tests incl. an axe accessibility check per component
npm run typecheck
npx shadcn build --output public/r # rebuild the registry after changing a component
Money sorting is tested with amounts past a float's precision: a float sort sees them as equal and fails the test (checked by putting one in).
Used by
The CRM's screens (erp-compositions crm/web) vendor these items from public/r with
scripts/vendor-erp-ui.mjs and record the commit they came from.
Not in 0.2
Charts, column visibility, URL state helpers, server-side pagination, a responsive card layout for wide tables on a phone (they scroll inside themselves today).
License: Apache-2.0, see LICENSE