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.

// nuxt.config.ts
export default defineNuxtConfig({
extends: ['@qpoint-io/q-nuxt-layer']
})

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.

// loads immediately
<UxModal :open="showSettings" />
// loads only when rendered — wrapped in defineAsyncComponent
<LazyUxModal :open="showSettings" />

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

Product

The 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

Marketing

The 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

Documentation

This 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*
UxButtonUxInputUxModalUxToggleUxCheckboxUxSimpleSelectUxTagUxCopyBtnUxCloseUxMessageUxIconBtn

Icons

UxIcon
arrow-headarrow-rightcheckcheck-fatcopyeditplussearchspinnerexternal-linklogo

Dev tools

Dev*
DevCanvasDevFrameDevHDevLabelDevCommentDevMetaPropsMachine

Prose

Prose*
ProseSectionHeadlineProseBigPointProseCheckProseStepProseStepWrapperProseHeroProseQuoteProseFAQProsePoint

Data display

Data*
DataBadgeDataBarDataHealthDotDataStatusPillDataSparklineDataProgressRing

Layout

Layout*
LayoutIconList

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

Tailwind CSSseparate docs repo
Carbon (IBM)separate docs repo
Radix UIseparate docs repo
GitHub Primerseparate docs repo

Monorepo, separate package

VuetifyNuxt docs app
PrimeVueshowcase workspace
Nuxt UINuxt docs app
Shopify PolarisNext.js docs app

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