Home / Guides / New feature module

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.

Guide Navigation + scaffold Returns worked example

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

PageShellBody blocks
LandingApp shellPage header + KPI grid + Highlights + Quick links
ListApp shellPage header + List page shell + Data toolbar + Data table
NewApp shellPage header (compact) + Form shell
DetailApp shellPage header (compact) + Detail hero + Detail tabs + Activity feed
SettingsSettings shellForm shell (per section)

Naming, URL, and sidebar conventions

File and folder naming

  • Module folder is a lowercase plural noun: returns/, not return/ or returnsModule/.
  • 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

QuestionAnswer
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

PageWhat's on itNotes
LandingToday's returns count · refund total · top reason chip · 8 recent returns tableUses KPI grid · Highlights
ListAll returns with status / branch / reason filters · row click opens detailStandard List page shell
NewStep 1: scan invoice. Step 2: pick lines. Step 3: refund method. Sticky bar.Three-section Form shell
DetailHero with refund amount + status. Tabs: Lines, Timeline, Refund, Comments.Detail tabs + Comment thread
SettingsReturn 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

Do

Answer the four questions before creating files. They take ten minutes and prevent two-week rewrites.

Don't

Build a module in kits/ "for now" and migrate later. The migration never happens; the wrong sidebar entry persists.

Do

Use the standard page set (index, list, new, detail, settings). Future authors find what they expect.

Don't

Invent new file prefixes. The system uses exactly four: p-, b-, x-, pg-.

Do

Put filters in query string, tabs in hash. Operators bookmark filtered views; deep links must work.

Don't

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.