ResourcesDesign system

Patterns

Live specimens of the patterns used across CargoHub: navigation, registers, records, feedback and states. Each one uses the real classes, so what you see here is what ships.

Sidebar states

Left menu for every desktop portal. The sample on the left is live: open the group, pick pages, pin and unpin.

live
default
hover
active · aria-current
focus-visible
with count
pinned
Live tracking
hidden item, found by search
rail + flyout (hover or focus a group)
One active item, marked with aria-current="page". The group that holds it keeps bold text so you can see where you are when it is closed.
Items hidden from the menu still appear in search, marked Hidden in your menu.
In rail mode, show the full label in a tooltip or flyout. Never show an icon with no name.
Don’t signal the active page with colour only. Use weight and the tint as well.
Don’t let pinning remove an item from its group. Pinned is a shortcut, not a move.

Menu editor rows

Customise menu, one row per item. The list is live: drag a row, or focus its handle and press ↑ / ↓.

live
Dashboard
BiltiesRenamed · was “Bilties (Bilty register)”
Dispatch board
Live tracking
Order saves when you release. Keyboard: Tab to a handle, then ↑ / ↓.
rest
Dispatch board
drag lift · --elev-3
Dispatch board
drop line
Dashboard
Live tracking
hidden
renamed
BiltiesRenamed · was “Bilties (Bilty register)”
Lift the row being dragged (--elev-3, a small tilt) and show a 2px drop line where it will land.
Give every row a keyboard route: a focusable handle that moves with ↑ / ↓ and announces the new position.
Show the original name under a renamed item, with a one-click reset.
Don’t reorder while the pointer moves. Move the row only when it is dropped.
Don’t delete hidden items. Hiding is reversible and the page stays in search.

Page header anatomy

Built with ph(crumb, title, sub, actions, extra). Every page uses it.

1TMSConsignments

Bilties (Bilty register) Live 2

Every bilty booked across branches, with payment basis, load and dispatch status. 3

4
1Crumb. UPPERCASE, “Module · Page”. It says where you are. It is not a link trail.
2Title. Names the thing or list. A status pill may follow it.
3Subtitle. The spec’s one sentence, at most 72 characters per line.
4Actions. Secondary actions on the left, one primary action on the far right.
One primary button per header. Put other actions in a secondary button or the ⋯ menu.
Don’t repeat the crumb text in the title, and don’t use a title that is only a record number.

Register table

The reg() scaffold: filter chips, search, sort, density, bulk bar. Sort a column, switch density, tick rows.

KHI/2026/01283
EFEngro Fertilizers
KHI → LHRBookedRs 2,02,252
LHR/2026/01284
NMNishat Mills Ltd
LHR → KHIBookedRs 1,16,348
FSB/2026/01288
STSapphire Textile
FSB → KHIBookedRs 92,394
LHR/2026/01287
KMKohinoor Mills
LHR → KHIBookedRs 77,408
LHR/2026/01285
ILInterloop Ltd
LHR → KHIBookedRs 49,560
LHR/2026/01286
DPDawlance Pvt Ltd
LHR → KHIBookedRs 44,014
Showing 1–6 of 46
Right-align money and quantities and use tabular numbers. Put doc numbers in mono chips.
Show the bulk bar only while rows are ticked, and always include Clear.
Mark the sorted column with aria-sort and an arrow.
Don’t hide row actions from keyboard users. The ⋯ menu shows on hover and on focus-within.
Don’t paginate under 25 rows or show empty pages.

Record page

recordPage(): a header with status, tabs on the left, a summary rail on the right. The tabs are live.

TransportBilties

LHR/2026/01284 Booked

Nishat Mills Ltd → Amreli Steels

Consignor
Nishat Mills Ltd
Consignee
Amreli Steels
Route
Lahore → Karachi
Load
PTL · 468 pkgs · 11,157 kg
Commodity
MS Steel Bars HS 7214
Put the money and the next step in the rail, and the details in tabs. Keep the rail the same on every tab.
Don’t put more than five tabs on a record. Split the record, or use sections inside a tab.

Empty, loading, error, offline, stale

Every list and widget has all five. Refresh the stale card to see the skeleton.

empty
No bilties yet

Book the first consignment. It shows up here with its GR number.

loadingLoading
Loading trips
errorFailed
offlineOffline
TR-408 · MNB-5520Last GPS 14 min ago near Multan. 3 actions are waiting on the phone and will sync when it reconnects.
stale dataStale · 2 min
TR-411 · ETA 30 Sep 14:10ETA from a ping 2 min 40 s old
Say what is missing and offer the next step. Keep the layout the same size as the loaded state so nothing jumps.
Show the age of stale data (“2 min 40 s old”) next to the value.
Use a skeleton for every simulated refresh, and mark the region aria-busy.
Don’t show a spinner over the whole page, and don’t clear good data while a refresh is running.
Don’t write “No data” or “Something went wrong”. Say what failed and what the user can do.

Confirm + Undo

Reversible actions ask once and offer Undo. Actions that can’t be reversed need a typed reason and never offer Undo.

Draft bilty KHI/2026/01290Nishat Mills Ltd → Lahore · saved 09:14
Draft bilty LHR/2026/01288Amreli Steels → Karachi · saved yesterday
Draft bilty FSB/2026/01287Interloop Ltd → Karachi · saved 27 Sep
Irreversible:
Name the object and the result in the dialog, and repeat the verb on the button: “Archive draft”, not “OK”.
After a reversible action, show a toast with Undo for 5 seconds and put the row back where it was.
Don’t ask “Are you sure?” for actions that can be undone. Do it, then offer Undo.
Don’t offer Undo for things the outside world has already seen, such as an FBR submission or an SMS.

Locked button + request access

What people see when their role can’t do something. decorateLocks() applies this to real buttons.

Locked buttons stay visible and focusable. Hover or focus shows what is needed. Clicking opens Request access, which goes to the owner.
Keep the button where it is, show a lock, and explain the permission it needs.
Let people ask for access from the same place, with a reason.
Don’t hide actions the role can’t take if people need to know they exist, and don’t use a disabled button with no explanation.

Service-mode banner

Shown under the top bar when CargoHub pauses a workspace. Write actions are locked automatically.

Setup modeOnly the owner’s setup work is allowed. Dispatch and operations open at go-live.setup_only
DrainingActive trips, picks and evidence can finish. New bookings, dispatches and finance postings are closed.draining
Read-onlyYou can view and export. All new changes are paused by CargoHub.read_only
SuspendedSign-in is paused for this workspace. Offline driver queues are kept for review.suspended
Say what still works, not only what is blocked. Keep the banner on every page while the mode is on.
Don’t use a dismissable toast for a state that lasts. Use this banner.

Toasts & snackbars

toast(msg, kind, {undo}). Bottom-right on desktop. Inside the phone in the driver app.

Invoice INV-2026-00318 posted
FBR didn’t answer. We’ll retry in 2 min.
Removed from Pinned
Confirm with the object: “Invoice INV-2026-00318 posted”. Success uses role=status, errors use role=alert.
Keep one line, show it for 2.8 s (5.2 s with Undo), and stack new ones on top.
Don’t put the only copy of an error in a toast. Show field errors next to the field.
Don’t show a toast for navigation or for things the user can already see changing.

Bottom sheet (driver app)

A phone mock. Documents, filters and actions open from the bottom, within thumb reach. 48 dp targets.

TR-412 · LHR → KHILES-4821 · Muhammad Aslam
Next stop
SITE Area, Karachi · ETA 18:40
3 bilties on board
KHI/2026/01284 +2
POD saved · will sync on signal
Anatomy
1Grab handle. 36 × 4 px, decorative. Swipe down or tap the scrim to close.
2Title. Says what the sheet holds.
3Rows. At least 48 px tall, status as text and colour.
4Close. A full-width button at the bottom, within thumb reach.
Motion: --dur-4 with --ease-emphasized. The scrim fades over --dur-3.
Use a sheet for short, related choices on the phone. Keep the page behind it visible.
Don’t stack a sheet on a sheet, and don’t put a long form in one. Open a full screen instead.

Peek card

Hover a GR, vehicle or trip link for 380 ms in the app to see a summary without leaving the page.

triggerHover KHI/2026/01283 in any list.
card
KHI/2026/01283Booked
Engro Fertilizersto Sapphire Textile
Route
Karachi → Lahore
Load
FTL · 109 pkgs · 19,563 kg
Total
Rs 2,02,252
Trip
Not dispatched
Show the 4–5 facts people check most, then let the click open the full record.
Don’t put actions in a peek. It is read-only and closes when the pointer leaves.

Flow strip

flowStrip(kind, record). It shows where a record is in order-to-cash, procure-to-pay or warehouse, and the next step.

Order to cash
2 of 7 done
Show one clear Next action. Done steps are links to their documents.
Don’t use the strip as navigation for unrelated pages, or show it on list pages.