Adding a new feature module
A feature module is a top-level navigable area — its own sidebar entry, several pages, and a clear owner. Setting one up cleanly takes about an hour and avoids weeks of rework.
Where it lives and who uses it
Four questions to answer before you create the first HTML file.
1 — Where does it live?
Three valid homes, in order of preference:
- Inside an existing vertical (Retail, Education, Mining): the module is specific to that line of business. Lives in
verticals/<vertical>/, appears in that vertical's sidebar. - Inside an existing portal (POS, Parent, Student, Teacher): the module is mobile-first and consumer-facing. Lives in
portals/<portal>/with the portal shell. - Shared across all verticals (Reporting, Audit, Settings): the module is generic ERP plumbing. Lives in
kits/and appears in every vertical sidebar.
If two verticals need a near-identical module, build it shared and let each vertical override copy. Forking is more expensive than refactoring later.
2 — How many pages?
A module typically has 3–5 pages and exactly one is the landing. Fewer than 3 → the module is probably a page in another module. More than 5 → the module is probably two modules.
3 — What's the primary record?
Every module has one. For Customers it's the customer; for Invoices it's the invoice. Every other page in the module either lists, edits, or supports that primary record. Naming the primary record clarifies which pages belong.
4 — Who reads it, who writes it?
The roles that consume vs the roles that author shape both the navigation entry (do cashiers see it?) and the permission gate (see Role gate). Document both in the module's overview page.
Scaffold a new module
The standard skeleton, copy-paste ready.
Files to create
verticals/retail/returns/ ├── index.html // landing — KPI grid + recent returns ├── list.html // all returns — data table ├── new.html // create return — form shell ├── detail.html // single return — detail + tabs └── settings.html // return policies — settings shell
Blocks each page composes
| Page | Shell | Body blocks |
|---|---|---|
| Landing | App shell | Page header + KPI grid + Highlights + Quick links |
| List | App shell | Page header + List page shell + Data toolbar + Data table |
| New | App shell | Page header (compact) + Form shell |
| Detail | App shell | Page header (compact) + Detail hero + Detail tabs + Activity feed |
| Settings | Settings shell | Form shell (per section) |
Naming, URL, and sidebar conventions
File and folder naming
- Module folder is a lowercase plural noun:
returns/, notreturn/orreturnsModule/. - Page files use the conventional names above:
index,list,new,detail,settings. Diverge only with a real reason. - System docs use prefixes:
p-components,b-blocks,x-patterns,pg-pages.
URL conventions
- Module root:
/returns/— lands on the dashboard. - List:
/returns/list?status=open&branch=harare— filters in query string. - Detail:
/returns/RET-2026-0142#timeline— record ID in path, tab in hash. - Create:
/returns/new?from=inv_8a32— pre-fill via query string. - Settings:
/returns/settings.
Sidebar entry
Add the module to the vertical's sidebar group with an icon from Iconography, a label in sentence case, and an optional "New" tag for the first month after release. Order modules by frequency of use, not alphabetically.
Worked example — adding "Returns" to Retail
A new module in the Retail vertical. Walk through the four questions and the page wiring.
Answers to the four questions
| Question | Answer |
|---|---|
| Where does it live? | Retail vertical — returns are POS-driven and refund flows differ per vertical. |
| How many pages? | Five: landing, list, new, detail, settings. |
| Primary record? | Return (RET-YYYY-NNNN). Linked to an Order and one or more Line items. |
| Who reads / writes? | Cashiers create on POS terminal; supervisors approve in dashboard; finance reads in audit. Three roles, two surfaces. |
Page wiring
| Page | What's on it | Notes |
|---|---|---|
| Landing | Today's returns count · refund total · top reason chip · 8 recent returns table | Uses KPI grid · Highlights |
| List | All returns with status / branch / reason filters · row click opens detail | Standard List page shell |
| New | Step 1: scan invoice. Step 2: pick lines. Step 3: refund method. Sticky bar. | Three-section Form shell |
| Detail | Hero with refund amount + status. Tabs: Lines, Timeline, Refund, Comments. | Detail tabs + Comment thread |
| Settings | Return window (days), restocking fee, manager-approval threshold. | Settings shell with one section |
Sidebar entry
Inserted into the Retail sidebar between Orders and Customers — the place a cashier looks when handing back money. refund icon, label "Returns", "New" tag for 30 days.
Permission gate
The module wraps in Role gate with retail.returns.read required for any view; create requires retail.returns.write; refunds above ZWG 500 trigger Approval flow.
Do & don't
Answer the four questions before creating files. They take ten minutes and prevent two-week rewrites.
Build a module in kits/ "for now" and migrate later. The migration never happens; the wrong sidebar entry persists.
Use the standard page set (index, list, new, detail, settings). Future authors find what they expect.
Invent new file prefixes. The system uses exactly four: p-, b-, x-, pg-.
Put filters in query string, tabs in hash. Operators bookmark filtered views; deep links must work.
Mix two primary records in one module. "Returns and refunds" is two modules — split them.
What's next
Each page in your module needs an empty / loading / error state and a mobile plan. See Compose a page and Mobile adaptation.