q-nuxt-layer
The shared component library that keeps Qpoint projects in sync — built once, composed everywhere.
Why it exists
Before the layer, components lived inside app.qpoint.io. We had a /dev playground for building them, but on integration the live component became the only working instance. The playground was perpetually broken — a preview that didn't preview anything.
When we started the brochure site, the same components were needed in a second project. Copy-paste meant drift. What we needed was a single source of truth for shared UI — something every project could extend without duplicating.
The layer is that source of truth. Components, tokens, and CSS are built once and consumed by every Qpoint project through a single extends configuration. Changes propagate automatically. Playgrounds stay alive because they're consuming the real thing.
What it provides
The layer ships four things. Nothing more.
Components
UX primitives (Button, Input, Modal, Toggle), icons, dev tools, prose/content blocks, data display, and layout components — 70+ total.
Design tokens
The full brand palette (Grape, Leaf, Grey), type scale, spacing scale, and border radii — all defined in a shared Tailwind config.
Shared CSS
Base styles, Tailwind directives, font imports, and utility classes that every project needs. Loaded automatically.
Composables
Shared logic like clipboard handling and responsive utilities — auto-imported alongside the components.
How it works
Nuxt Layers are additive. A consumer project adds one line to its config and inherits everything the layer provides — components auto-import, tokens merge into Tailwind, and shared CSS loads automatically.
That's the entire integration. No plugin registration, no manual imports, no build configuration. The consumer project gets UxButton, UxInput, UxModal, and every other shared component as if they were defined locally.
The layer is additive, not prescriptive.
Any consumer can override a layer component by defining a local component with the same name. Nuxt auto-import gives local components higher priority. You inherit the system until you choose to depart from it.
Lazy loading
Performance-sensitive projects like app.qpoint.io rely on lazy loading — heavy components should only load when they're actually rendered. The layer fully supports this because Nuxt's auto-import system gives every registered component a free Lazy prefix.
The layer ships the component code, but the consumer decides when to load it. Use the component name directly for eager loading, or prefix it with Lazy to defer it. No configuration needed — it works for every layer component automatically.
Unused components cost nothing
The layer ships 70+ components, but each consumer only pays for what it uses. Nuxt's auto-import is a compile-time transform — when the template compiler encounters a component tag, it injects a specific import for that component. Components never referenced in any template are never imported. Their JavaScript, templates, and styles are completely excluded from the production bundle. Full verification research.
JS & templates of unused components
Completely excluded. Nuxt never injects the import, so Vite/Rollup never sees the code.
Scoped CSS of unused components
Excluded. Never imported, and Vite's cssScopeTo feature provides a second layer of elimination.
Global CSS entries (shared.css)
Always included — CSS registered in the layer's nuxt.config is unconditional. This is intentional: it provides the base styles every consumer needs.
Tailwind utilities from unused component source files
Marginal cost. Tailwind's JIT scans source files, not the bundle, so classes in unused components may generate a few extra utility declarations. Negligible in practice.
This is what makes the unified pantry work. Domain-specific components (security cards, data-metric stats) can live alongside generic primitives (buttons, inputs) without bloating projects that don't use them. Stock ingredients once, pay only for what you cook.
The ecosystem
Three projects consume the layer today. Each has a different purpose, but they share the same foundational components and visual language.
app.qpoint.io
ProductThe main application. Uses layer components for all UI — buttons, forms, modals, data tables, toggles. Product-specific components compose on top of the shared primitives.
www.qpoint.io
MarketingThe brochure site. Consumes the same brand tokens and base components, plus prose components for rich content layouts. DRY across product and marketing.
design.qpoint.io
DocumentationThis site. Interactive component documentation, brand guidelines, pattern library, and visual archive — all built with the components it documents.
What's in the box
The layer's components are organized by role. Each group has a clear purpose — no component exists without a reason to exist. See the full interactive catalog on the components page.
Interactive
Ux*Icons
UxIconDev tools
Dev*Prose
Prose*Data display
Data*Layout
Layout*Deep dives
Key design decisions
The layer's architecture reflects deliberate choices. Each has a reason.
Why a Nuxt Layer instead of a component library?
A layer extends the consuming project — components auto-import, tokens merge, CSS loads. No registration boilerplate. The consumer doesn't install a library; it inherits a foundation.
Why composition over configuration?
Components use slots for flexible content, not deep prop trees. UxButton has an icon slot, not iconName + iconPosition + iconSize. This keeps interfaces simple and lets consumers compose freely.
Why do docs live in the design site, not the layer?
The layer is a library — it ships components. Adding showcase pages would bloat what gets published. The design site is purpose-built for documentation, and it demonstrates the layer by consuming it.
Why "replicate first, optimize later"?
When extracting a component from a sister project, we match existing behavior exactly. Understanding what you have is the first step. Premature optimization locks in assumptions before the problem is understood.
Why local overrides instead of configuration?
Any consumer can replace a layer component by defining a local one with the same name. This is simpler than config flags and lets projects depart from the system when they need to — without forking it.
A proven pattern
Separating the component library from its documentation site is the dominant pattern across major design systems. The library ships components. A separate project consumes the library and documents how to use it.
Separate repos
Monorepo, separate package
In the Vue/Nuxt ecosystem specifically, this is the standard.
Vuetify, PrimeVue, Quasar, and Nuxt UI all maintain their docs site as a standalone app that consumes the component package — exactly like this site consumes q-nuxt-layer.
Separation of concerns
Each project in the ecosystem has a distinct role. The layer is the connective tissue — it doesn't try to be everything.
q-nuxt-layer
Shared components, design tokens, composables, and CSS. The building blocks. No business logic, no documentation, no examples.
design
Brand identity, component documentation, pattern library, visual archive, and the MCP server. The reference implementation.
app.qpoint.io
Product logic, data persistence, API integrations, and product-specific compositions. Consumes the layer, doesn't define the system.
www.qpoint.io
Marketing content, SEO, and rich editorial layouts. Same components and tokens, different compositions.
Living documentation
The design site — this site — serves as the reference implementation. It consumes the same layer components that app.qpoint.io and www.qpoint.io use. Every demo on the components page is a real instance, not a screenshot or mockup.
The feel of the wheel seals the deal. When a colleague or stakeholder sees a component demo, they're interacting with the actual component — the same code that runs in production. The demo can't diverge from reality because it is reality.
Before and after
Before
- Components lived inside app.qpoint.io only
- Dev playground diverged on integration
- Live instance was the only working copy
- Brochure site duplicated component code
- No shared tokens — colors drifted between projects
- Documentation was the code itself
After
- Components live in a shared layer
- Every consumer runs the real component
- Changes propagate to all projects automatically
- Brand and base components are DRY
- One Tailwind config, one token palette
- Interactive documentation on this site