UIPackage
Menu

Framework

Change language

Boilerplate repo
Back to Blocks Gallery
Architecture & Craft Discipline

The Block Mental Model

In an unbundled component registry, the source code is the product, not an npm package. Consumers install files directly into their repository and own them permanently.

To ensure components remain modular, readable, and easy to modify, UIPKGE strictly enforces boundaries between three architectural layers: Primitives, Blocks, and Pages.

1. The Three Architectural Layers

Every item in UIPKGE belongs to one distinct layer with its own clear responsibilities.

registry:uiTier 1

Primitive

Abstracts low-level mechanics: focus rings, keyboard navigation, ARIA states, theme tokens, and animation physics.

Props change behavior:
variant, size, disabled, multiple, smooth
Examples:
ButtonCardSparklineKpiGrid
registry:blockTier 2 · Focus

Block

Composes primitives into a self-contained UI section or interaction pattern. Internals are visible and top-to-bottom so they can be customized instantly.

Props change data/labels:
title, defaultSeed, onSelect, columns
Examples:
activity-feedkpi-dashboardpricing-table
registry:pageTier 3

Page Template

A full-viewport route template or multi-feature console. Bundles global layout shells, persistent sidebars, header chrome, and multi-view navigation.

Provides route orchestration:
navigation shell, multi-tab suites, routes
Examples:
login-01settings-pagedata-explorer

2. The Six Principles of Block Craft

When building or maintaining blocks in UIPKGE, follow these six non-negotiable standards.

01

A Section, Not a Route Shell

A block is designed to be embedded into an existing screen. It must never assume full viewport control (min-h-screen), full-page sticky sidebars, top navbars, or URL routing. If an item requires an entire browser viewport and coordinates multiple disparate workflows, it is classified as registry:page.

02

Transparent & Hackable ("Own Your Code")

In an unbundled registry, users copy the code to customize it. Blocks must read top-to-bottom at the call site. Tile titles, metric figures, badges, and action buttons should be visibly declared inline. Never create opaque wrapper primitives that hide HTML markup behind deep, generic configuration objects.

03

Single Discrete Workflow

Each block should solve exactly one user job to be done (e.g. "Review and approve contractor timesheets", "Inspect database schema drift", or "Configure environment variables"). Never bundle 4 unrelated sub-applications (billing, audits, user management, and telemetry) into a single monolithic block.

04

Scoped State & Zero Store Dependencies

A block manages its own interactive UI state (search filters, sorting, tab selections, modal toggles) using standard local reactivity (ref / useState). It must never assume global external state managers (Pinia, Redux, Zustand) or hardcoded backend fetch endpoints.

05

500 LOC Soft Cap & Multi-File Architecture

Single block files have a 500 LOC soft cap to ensure they can be understood in a single reading. When a block is genuinely rich (e.g. kanban-task-board or virtual-tour-panorama), decompose it into co-located subcomponents and register all files in the sidecar manifest.

06

Minimal Inline Seed Data ("Data-Out")

Component markup should not be drowned in 300 lines of hardcoded mock records. A block ships with a lean default seed (1–3 realistic items) or extracts fixture datasets to a dedicated companion file (<block-name>-data.ts). Rich multi-scenario fixtures belong in Astro demo stories.

The Decision Gate

The Litmus Test: Block vs. Primitive vs. Page

Ask these three questions when designing, auditing, or implementing any new component:

Question 1
Can this be dropped into a dashboard grid?

If yes without breaking the surrounding layout, it is a Block. If it requires full viewport height and app-level navbars, it is a Page.

Question 2
Does it compose multiple primitives?

If it combines Cards, Badges, Buttons, and Sparklines into a visible layout, it is a Block. If it only handles mechanics and focus for a single element, it is a Primitive.

Question 3
Can a user modify a badge in 30 seconds?

If the JSX or template reads top-to-bottom with visible markup, it meets our craft bar. If changes require reverse-engineering generic wrapper props, it is over-abstracted.

3. Patterns & Anti-Patterns

Comparison of layout-hiding abstractions versus raw primitive composition.

✕ Anti-Pattern: Layout HidingOver-abstracted
<!-- BANNED: Layout-hiding generic props -->
<StatGrid
  :data="kpis"
  title-key="label"
  metric-key="val"
  trend-key="diff"
  card-variant="outline"
/>

<!-- Consumer cannot easily:
  - Add a tooltip to card #2
  - Swap an icon for an avatar
  - Change the font size of one metric
-->
✓ Craft Standard: Composed PrimitivesHackable & Explicit
<!-- COMPLIANT: Composed Primitives -->
<KpiGrid class="grid-cols-1 sm:grid-cols-3">
  <Card class="p-4">
    <div class="flex items-center justify-between">
      <span class="text-xs text-muted-foreground">Revenue</span>
      <Badge variant="outline">+14.2%</Badge>
    </div>
    <div class="mt-2 text-2xl font-bold">$48,200</div>
    <Sparkline :data="trend" class="mt-3 h-8" />
  </Card>
</KpiGrid>

4. Canonical Block Directory Structure

Standard packaging layout in both packages/registry-vue/blocks/ and packages/registry-react/blocks/:

packages/registry-{vue,react}/blocks/<name>/
<Name>.vue (or <Name>.tsx) — Main composed block component (<= 500 LOC)
<Subcomponent>.vue — Optional co-located subcomponents for drawers/dialogs
<name>-data.ts — Extracted mock dataset (keeps component source lean)
<name>-types.ts — Shared TypeScript types and interfaces
index.ts — Clean barrel exports for consumers
<name>.registry.ts — Manifest defining all dependencies and distributed files
Questions or looking to contribute a new block?