Reuse audit
Where the design system is reusable today, and where it’s not — a living roadmap. Every finding cites a concrete file or class so a refactor can start the same day.
Top-line summary
The headlines. Each is expanded in the findings below.
- The 5 new lift-ups landed cleanly —
b-bottom-tabs,b-filter-chips,b-row-card,b-stat-hero,b-day-list. All are token-only and replace 9 portal-bespoke prefixes (.gd-tabs/.pa-tabs/ etc.). Adoption is real but partial — 4 portals still ship the old aliases (see Finding D-1). - Three overlay primitives have overlapping surfaces.
.modal-card,.modal-window, and.x-bottom-sheetall define a centred dialog on desktop. The desktop adaptation in.x-bottom-sheetalready is the modal pattern — keep one canonical surface. - Six primitives have a doc page but no entry in primitives.html or the sidebar:
p-table,p-tabs,p-tag,p-toast,p-skeleton,p-pagination. Thirteen pattern pages (x-bulk-edit,x-approval-flow, …) are likewise unreachable from the sidebar. The catalogue is wider than the menu suggests. - Four "shell" blocks live in
/system/b-*-shell.htmlbut never appear on the Shells page. If something is a shell, it belongs on shells.html — and probably wants thex-prefix, notb-. - p-alert and b-callout render the same markup with inline styles — neither uses the
.alertclass that ships in components.css. Pick one (probably.alert), expose it as the primitive, and letb-calloutbe a composition shortcut. - Status tone vocabulary is inconsistent. The system mixes
tone-success(tokens),badge-success/.badge.success/.alert.success(primitives) and.erp-pill.sold/.erp-pill.assigned(semantic-but-domain-specific). Need one canonical tone vocabulary. - The "stat" family has three primitives that overlap:
.stat-tile,.stat-spark, and the new.b-stat-herosecondary tiles. The first two should be merged or one should be deprecated. - Hard-coded hex slips into components.css in 4 places (
#BFB9A8,#6B655A,#2E5526,#7A2419). Small, but the "tokens-only" promise is the load-bearing one — these should be lifted into tokens.css.
Duplicates
Two or more primitives/blocks solving the same problem with different APIs.
D‑1 · Modal, Bottom sheet, and the modal-window all draw a centred desktop dialog
Evidence. .modal-card (components.css line 894), .modal-window (line 2005), and .x-bottom-sheet with its @media (min-width: 720px) desktop adaptation (line 2434) each define a centred dialog with a header / body / footer split. The patterns docs at x-modal.html and x-bottom-sheet.html use inline styles to draw the same shape a third time.
Why it hurts. Recipe authors can’t tell which to reach for. A "centred confirmation modal" recipe in the cookbook will likely re-invent a fourth one.
Recommendation. Promote .x-bottom-sheet as the single overlay primitive (it’s already responsive). Treat .modal-card as a legacy alias and document the desktop variant as the canonical "modal." Delete .modal-window after its one use (Admin settings modal) is migrated.
Evidence. Both pages render display: grid; grid-template-columns: 32px 1fr; padding: 14px 18px; background: var(--tone-info-bg)… as inline style. Neither uses the .alert class defined in components.css line 717. A grep across portal demos finds 2 uses of .alert and 0 of .b-callout — the primitive class shipped but is rarely consumed.
Recommendation. Make .alert the one true class. Rewrite both reference pages to use it. Demote b-callout to "composition: alert with action button" or fold it into p-alert as Composition 04.
D‑3 · Three "stat" surfaces compete
Evidence. .stat-tile (line 1024), .stat-spark (line 1545), b-stat-card, and the secondary tiles inside .b-stat-hero .b-sh-s (line 2300) all draw the same primitive: label + big tabular number + delta.
Resolution. Canonical surfaces picked: b-stat-hero for hero-with-one-lead, b-stat-card for individual KPI tiles. b-kpi-grid marked deprecated (banner + relabel on blocks.html); .kpi-grid CSS retained as alias forward.
D‑4 · Five filter-chip implementations
Evidence. The system ships .chip (line 109, primitive), .fchip (line 986, “toggleable”), .tag (line 974, “with X”), and the new .b-fc inside .b-filter-chips (line 2167). Plus the legacy .filter-chips / .hist-filter still live in 2 portal demos.
Resolution. b-filter-chips picked as canonical (banner added). Legacy .filter-chips, .chip-bar, .hist-filter markup forwarded via deprecation aliases in components.css.
D‑5 · Two dropdown menus, three nearly-identical popovers
Evidence. .menu (line 812) and .dropdown (line 1567) are the same component with different class prefixes. .context-menu (line 1614) is a 4-px-smaller copy. .user-menu (line 1641) is the same again with a header block.
Resolution. Canonicals picked: p-dropdown-menu (uses .dropdown) for menus; p-popover for popovers. Banners added to both. .menu, .context-menu, .user-menu, .hover-card, .pop-card retained as alias forwards.
D‑6 · Two table primitives
Evidence. .table (line 594) and .dtable (line 1114, comment: "supersedes earlier .table for ERP use"). The comment admits the duplication.
Resolution. Documented the relationship on both pages: x-data-table is the canonical full-feature data grid (uses .dtable); p-table remains the bare HTML semantic primitive (uses .table). Both kept; cross-links added; .data-table aliased to .dtable.
D‑7 · Three tab strips
Evidence. .segmented (line 341, equal-width sidebar tabs), .seg-tabs (line 862, inline tabs), .iconstrip (line 1077, with-icon tabs), and .under-tabs (line 1100, underlined). Four variants for what users perceive as "tabs."
Resolution. p-tabs picked as canonical (banner added). Legacy .kit-tab, .tabs, .tabstrip markup forwarded via deprecation aliases in components.css. .seg-tabs / .iconstrip / .under-tabs retained as variant CSS underneath the unified API.
Naming inconsistency
Same concept, different name. Trivial to fix and high payoff for searchability.
N‑1 · Tone vocabulary drifts across primitives
Evidence. The system uses five conventions for the same tonal axis: --tone-success (tokens), .badge-success (modifier class), .alert.success (compound modifier), .donut.success / .progress.success / .toast.success / .sparkline.success (postfix), and .erp-pill.sold / .erp-pill.assigned (domain words).
Recommendation. Adopt one shape: .x.tone-success / .x.tone-warn / .x.tone-danger / .x.tone-info. This matches the token names already used. Map .erp-pill.sold → .erp-pill.tone-success and similar.
/* before */ <span class="badge badge-success">Paid</span> <div class="alert success">…</div> <div class="donut warn">…</div> /* after */ <span class="badge tone-success">Paid</span> <div class="alert tone-success">…</div> <div class="donut tone-warn">…</div>
N‑2 · p-input documents "Input & field"
Evidence. p-input.html doubles as the home for .input, .textarea, .field, .field-label, .field-help, and .field-error. The nav label Input & field gives this away.
Recommendation. Rename the file to p-field.html (or split: p-input = the control, p-field = the label/help/error wrapper). Field is the more useful term — every form composes .field > .input.
N‑3 · b-callout vs p-alert vs .alert
Evidence. The doc page is b-callout, the primitive page is p-alert, the CSS class is .alert. Three names for one component. See also D‑2.
Recommendation. Pick "alert" everywhere (component vocabulary is well established).
Inconsistent APIs
Different prop or modifier shape for the same concept.
A‑1 · Mobile lift-ups are .b-*; the desktop counterparts are .x-*; the unprefixed primitives are everything else
Evidence. The five new lift-ups follow .b-* with descendant prefixes (.b-rc-nm, .b-sh-l, .b-fc-ct). The older blocks use no consistent prefix (.activity-card .ac-h .ti, .detail-card .dc-h .ti). The result: .ti means three different things across the system.
Recommendation. Adopt the new convention everywhere: every block’s internal classes are .{block-prefix}-{role}. Migrate .detail-card to .b-detail-card, .dc-h .ti → .b-dc-h .b-dc-ti. This is a big diff, so phase it.
A‑2 · Modifier surface varies by primitive
Evidence. .btn-sm, .btn-lg (postfix). .avatar.sm, .avatar.lg (compound class). .badge-info, .badge-success (postfix). .alert.info (compound). Three shapes mixed without a rule.
Recommendation. Codify: size = postfix (btn-sm, avatar-sm), tone = compound (.btn.tone-danger), variant = postfix (btn-ghost). Document on primitives.html as a one-liner.
A‑3 · .b-bottom-tabs is position: fixed in CSS but only documented inside a stage
Evidence. components.css line 2117 sets position: fixed; left: 0; right: 0; bottom: 0. The doc page b-bottom-tabs.html wraps the example in .bt-stage and uses an extra rule to override the fixed positioning.
Recommendation. Either ship .b-bottom-tabs--inline as a modifier (for use inside fixed-height phone-stage previews), or move the positioning to a wrapper (.b-bottom-tabs-bar > .b-bottom-tabs) so the inner element doesn’t carry positioning.
A‑4 · .x-bottom-sheet footer buttons re-implement .btn
Evidence. Lines 2418–2432 define .x-bottom-sheet .x-bs-foot button.primary, .alt, and .danger with their own colours. These shadow .btn-primary, .btn-secondary, .btn-destructive.
Recommendation. Drop the local rules; require footers to use .btn .btn-primary. Keeps tone changes (e.g. updating --action-primary-bg) propagating everywhere.
Over-specialised blocks
Blocks that hard-code one product’s decisions and so don’t travel.
O‑1 · .erp-pill carries domain words as modifiers
Evidence. Lines 1036–1055: .erp-pill.on-site, .erp-pill.sold, .erp-pill.committed, .erp-pill.mapping, .erp-pill.unmapped, .erp-pill.failed. Half of these are tones in disguise; half encode product state that doesn’t exist outside the gold/scrap verticals.
Recommendation. Reduce to tone modifiers (.erp-pill.tone-success) and let the consuming vertical own its own status → tone map. Document the mapping table on the vertical’s page (gold = on-site → info, sold → success, …).
O‑2 · b-detail-hero assumes a 64×64 brand mark
Evidence. .detail-hero grid is grid-template-columns: 64px 1fr auto. The .dh-mark child has three baked-in variants (.gem, .ledger, .person) — gold-mine words baked into a "generic" block.
Recommendation. Replace the named variants with tone (.dh-mark.tone-warn = the warm gold gradient) and let consumers pick the icon.
O‑3 · b-master-data-shell is a shell, not a block
Evidence. Four files match /system/b-*-shell.html: list page shell, detail page shell, form shell, master data shell. None appear on shells.html — that page only documents the dashboard / portal shells.
Recommendation. Either rename to x-list-page-shell etc. and surface on shells.html, or rename to b-list-page (drop "shell" to keep them as composable blocks). Today they straddle.
Missing primitives
Patterns the cookbook will want to compose. None exist as a primitive yet.
M‑1 · Sticky save bar
Evidence. The form shell doc mentions "sticky action bar" but no primitive ships. A grep for save-bar, save-strip, action-bar across portals returns 0 — every form re-invents it inline. The "Forms · Autosave drawer form" recipe (placeholder in cookbook) will need this.
Recommendation. Ship .b-save-bar (full-width sticky bottom strip: dirty-state label on the left, Cancel + Save on the right). Tokens for height, padding, and shadow.
Resolution. Shipped as the .p-save-bar primitive — see Save bar. Sticky bottom, slide-up on .dirty, safe-area-inset aware.
M‑2 · Pagination
Evidence. p-pagination.html ships as a primitive page but is not linked from primitives.html or the sidebar. .pager-row (line 1164) is the only paging-related CSS and it only handles the "showing X–Y of Z" label, not number buttons.
Recommendation. Expose p-pagination in the nav and add CSS for the number-button row (.pager-row .pager-nums button).
Resolution. p-pagination is now linked from the sidebar and the primitives index. .p-pagination wrapper class added — count, page-size, and numbered nav in one row.
M‑3 · Empty state for inside a card
Evidence. .empty-state (line 910) defaults to padding: 56px 32px — appropriate for a full page but heavy when nested in a small b-card. Inline overrides are common in the portals.
Recommendation. Add .empty-state.empty-state--compact with padding: 24px 16px.
Resolution. Shipped as a sibling primitive — see Inline empty. .p-empty-inline stays under 80 px tall; icon · message · optional CTA in a single row.
M‑4 · Drag-handle / grabber
Evidence. .x-bs-grab lives inside the bottom sheet but the same 38×4 pill appears in 9 portal demos as a one-off (bottom-sheet doc, role switcher sheets, etc.).
Recommendation. Lift the rule to a top-level .grab-handle primitive so any sheet-like surface can adopt it.
Resolution. Lifted as .p-grabber (reorderable rows) and .p-grabber-pull (sheet pull tab). See Grabber.
Token gaps
Hard-coded values that should resolve through tokens.css.
T‑1 · Four bare hex values in components.css
Evidence. #BFB9A8 on lines 202, 223 (input hover border — pre-rebrand warm-paper holdover). #6B655A on lines 80, 84 (the soft-destructive button). #2E5526 and #7A2419 on lines 1318 / 1321 (diff add/remove text colours).
Recommendation. Add --border-hover, --action-destructive-soft-bg, --tone-success-strong, --tone-danger-strong to tokens.css.
T‑2 · Shadow recipes hard-coded in 6 places
Evidence. 0 1px 0 rgba(42,38,34,0.06) (active segmented), 0 -6px 18px -8px rgba(22, 24, 29, 0.10) (bottom tabs), 0 -20px 40px -10px rgba(22, 24, 29, 0.30) (bottom sheet) all bypass --shadow-rest / --shadow-popover / --shadow-modal.
Recommendation. Add --shadow-bar-bottom and --shadow-sheet-bottom.
T‑3 · Magic font sizes in b-* blocks
Evidence. .b-bottom-tabs uses 10.5px, .b-fc uses 12.5px, .b-row-card .b-rc-meta uses 10.5px. These sit outside the type scale defined in tokens.
Recommendation. Add a --type-caption-xs (10–11px) role or accept --type-caption (12px) and round these to the scale.
Documentation gaps
Files that exist on disk but aren’t reachable from the indexes or sidebar.
G‑1 · Six primitives missing from primitives.html and the sidebar
Files affected.
p-table,
p-tabs,
p-tag,
p-toast,
p-skeleton,
p-pagination.
These pages render fine in isolation but cannot be found through normal browsing.
Recommendation. Add cards to primitives.html and entries to huchu-nav.js. Trivial, high impact.
G‑2 · Thirteen patterns missing from the sidebar
Files affected.
x-approval-flow,
x-audit-view,
x-bulk-edit,
x-detail-tabs,
x-executive-dashboard,
x-help-center,
x-import-wizard,
x-master-data,
x-notifications,
x-offline-runtime,
x-onboarding,
x-role-gate,
x-settings.
Recommendation. Add to the Reference · Patterns section in huchu-nav.js. patterns.html does index most of them.
G‑3 · Page-shell blocks not on shells.html
Files affected.
b-list-page-shell,
b-detail-page-shell,
b-form-shell,
b-master-data-shell.
Indexed from blocks.html but absent from the dedicated shells page. See also O‑3.
Recommendation. Cross-link from shells.html (these are the per-page shells that sit inside the portal app shell).
G‑4 · New blocks not in blocks.html
Files affected.
b-bottom-tabs,
b-filter-chips,
b-row-card,
b-stat-hero,
b-day-list,
b-pricing,
b-pricing-card.
Recommendation. Add cards to the existing groups on blocks.html. The recent lift-up round shipped doc pages but didn’t update the index.
G‑5 · x-bottom-sheet missing from patterns.html
Evidence. The page exists and is linked from the sidebar, but the index page patterns.html doesn’t list it.
Recommendation. Add a card.
Recipe gaps
What the cookbook will want from the system. Flagged so primitive work can lead recipe work, not lag it.
R‑1 · The "Multi-step wizard" recipe needs a tokenised stepper variant
Evidence. .steps (line 1344) exists and works for horizontal flows. The recipe will likely need a vertical variant for the side rail.
Recommendation. Add .steps.steps--vertical.
Resolution. Shipped p-stepper with three step states (.done / .current / .pending) and two layouts (compact pill row + .p-stepper--labelled). The Multi-step wizard recipe now points at it instead of rolling a custom .ph-stepper.
R‑2 · "Inline edit on row" needs a save/cancel inline pattern
Evidence. .confirm-inline exists for delete confirms but there’s no parallel for "you have unsaved changes in this row." This is the load-bearing pattern for the lists recipes.
Recommendation. Extract from the in-flight master-data pattern as .b-inline-edit.
Resolution. Shipped .p-inline-edit in components.css — focus-ring shell + .p-inline-edit-save / .p-inline-edit-cancel icon buttons. The Inline edit on row recipe now includes a Step 3.5 that composes EditableCell against it.
R‑3 · "Optimistic + rollback" needs a transient toast tone
Evidence. .toast.success / .toast.danger exist; an "optimistic" / "in-flight" tone (subtle pulse, neutral ink) doesn’t. The state recipe will need it.
Recommendation. Add .toast.tone-progress using --brand-soft.
Resolution. Shipped .toast.tone-transient in components.css — white surface, 1.5s fade after a 1.5s read window, action button (e.g. "Undo") styled as the loudest element with --brand-soft. The Optimistic + rollback recipe’s soft-delete snippet now uses it.
R‑4 · "Role & permission" recipe needs a switcher primitive
Evidence. No .role-switcher exists. x-role-gate.html documents the gate but not the switcher. Several portals roll their own.
Recommendation. Ship a .b-role-switcher block (avatar + role label + chevron) reusing .workspace-btn.
Resolution. Shipped p-role-switcher — pill segmented control with aria-pressed wiring, two or three role buttons. Generic over any role enum. The Role & permission gate recipe references it for demo previews and dev banners.
Roadmap
Sequenced refactors. Now = this sprint, Next = before the next big feature, Later = when a recipe forces it.
Now
- Link the orphan docs into the sidebarWhy now: nothing should ship without nav. 19 pages are unreachable today (G‑1, G‑2).Unlocks: discoverability for every contributor.
- Merge p-alert + b-callout + .alertWhy now: cleanest possible win — three names, one component (D‑2, N‑3).Unlocks: the "Empty, loading, error" recipe.
- Pick the canonical overlay surfaceWhy now: three modals confuse every recipe author (D‑1).Unlocks: cookbook can write one "show a dialog" rule.
- Add cards on blocks.html for the 7 new blocksWhy now: the catalogue is wrong if the index is stale (G‑4).Unlocks: people stop missing the lift-ups.
Next
- Canonical tone vocabulary across primitivesWhy next: it’s a global rename — better when no big feature is in flight (N‑1, A‑2).Unlocks: theme tokens become trustworthy.
- Ship .b-save-bar primitiveWhy next: the form recipes are on deck (M‑1).Unlocks: "Autosave drawer form", "Multi-step wizard" recipes.
- Promote page-shell blocks to shellsWhy next: clarifies the b/x/s contract before the cookbook hard-codes today’s naming (O‑3, G‑3).Unlocks: shells.html as the single shell index.
- Merge .menu and .dropdownWhy next: one of them is dead weight (D‑5).Unlocks: smaller catalogue, easier onboarding.
Later
- Block-prefix migration for old blocksWhy later: big diff with no behaviour change — wait until a token sweep (A‑1).Unlocks: .ti / .h / .body unambiguous.
- Lift bare hex into tokensWhy later: invisible to consumers; bundle with the next token-pass (T‑1, T‑2).Unlocks: theming & dark mode tractable.
- Reduce .erp-pill to tonesWhy later: needs coordination with verticals (O‑1).Unlocks: verticals own their status → tone map.
Versatility scorecard
The eight most-reusable patterns and how they’re holding up. Portals = portal demos that actually consume the class.
| Pattern | Portals | API stable | Token-only | Documented | Recommendation |
|---|---|---|---|---|---|
b-bottom-tabs |
9 | Y | Y | Y | Add an --inline variant for previews (A‑3); remove the 4 legacy aliases from portals (D‑1). |
b-filter-chips |
10 | Y | Y | Y | Pick this as canonical (D‑4); deprecate .fchip. |
b-row-card |
11 | Y | Y | Y | Strongest of the new lift-ups. Migrate the 9 remaining .row-card sites. |
b-stat-hero |
9 | Y | Y | Y | Replace secondary tiles with a shared .stat primitive (D‑3). |
b-day-list |
5 | Y | Y | Y | Healthy. Watch for divergence in the 5 ad-hoc dt/dd sites still on old markup. |
x-bottom-sheet |
15 | Y | N | Y | Drop the local footer-button rules (A‑4); promote desktop variant as canonical modal (D‑1). |
b-page-header |
10 | Y | Y | Y | Solid. Used everywhere; document the "no actions" minimal variant. |
b-data-toolbar |
6 | Y | Y | Y | Good. Couple it with .dtable on a single x-data-table recipe. |
This page is living.
Update findings as they’re closed; add new ones as the system grows. The audit is only useful if it stays current — flag each finding as resolved in place rather than removing it, so the history of what was duct-tape stays visible.