@zoobzio/foundation
v1.0.0
Published
A design system for Vue 3 + Nuxt, delivered as a **single Nuxt layer**. Foundation spans the full range from unstyled interactive components built on semantic HTML up to stateful, generic data widgets — consumed by extending one layer.
Readme
@zoobzio/foundation
A design system for Vue 3 + Nuxt, delivered as a single Nuxt layer. Foundation spans the full range from unstyled interactive components built on semantic HTML up to stateful, generic data widgets — consumed by extending one layer.
Usage
Extend Foundation from your app's nuxt.config:
export default defineNuxtConfig({
extends: ["@zoobzio/foundation"],
});Pre-1.0 — published to npm as an early alpha under the
alphadist-tag, so install it explicitly:pnpm add @zoobzio/foundation@alpha. A0.xrelease is the running alpha; expect breaking changes between minors until 1.0.
Architecture
Foundation is one Nuxt layer rooted at app/, organized into tiers by responsibility:
| Tier | Directory | What it is |
| ---------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Components | components/core/ | Stateless interactive components composing reka-ui primitives and semantic HTML (f-* classes), with full passthrough & slotthrough. |
| Widgets | components/data/ | Definition-driven, generic data widgets (autocomplete, table, chart, deck, form, preview). |
| System | components/system/ | App-shell composition (workspace layout). |
Data widgets
Each widget pairs a definition (defineTable, defineForm, …) — inert typed config, storable in a constant — with a widget composable (useTable, useForm, …) that instances it in setup, and a component that renders it:
DataAutocomplete— stepped, suggestion-driven autocomplete inputDataTable— paginated, sortable, filterable data gridDataForm— programmatic form overTwith zod validationDataChart— configurable chart visualizationsDataDeck— infinite-scroll card feedsDataPreview— code / markdown content viewer
Imports
Auto-import is disabled — everything is imported explicitly. Inside the layer, modules import each other by relative path; a consumer app imports Foundation modules through the package's subpath exports:
import Select from "@zoobzio/foundation/components/core/select.vue";
import { useTable } from "@zoobzio/foundation/factories/table";
import type { SelectProps } from "@zoobzio/foundation/types/core/select";Framework symbols (Vue, Nuxt, VueUse) come from Nuxt's virtual #imports.
Project structure
app/
components/
core/ — interactive components (30)
data/ — data widgets: autocomplete, table, chart, deck, form, preview
system/ — app-shell composition (2)
composables/ — usePassthrough, useContext, useModel, useHooks, …
factories/ — widget composables: use*(id, definition) → Widget
services/ — feature logic classes (the unit under test)
stores/ — useState-backed feature state
plugins/ — log, tokens
types/ — per-component prop/emit types
utils/ — pure helpers (dates, formatting, passthrough merge, …)
constants/ — shared constants
app.vue · error.vue · app.d.ts
tests/ — vitest suite mirroring app/ (see tests/README.md)
nuxt.config.ts — layer config (auto-import off)Development
pnpm install
pnpm dev # run the layer in a Nuxt dev server
pnpm test # run the vitest suite
pnpm typecheck # nuxi typecheck
pnpm lint # eslint (lint:fix to auto-fix)Or via make (make help lists all targets):
| Command | Description |
| ---------------- | ----------------------------- |
| make install | Install dependencies |
| make dev | Start the Nuxt dev server |
| make lint | Run ESLint |
| make lint-fix | Run ESLint with auto-fix |
| make typecheck | Type-check (nuxi typecheck) |
| make test | Run all tests |
| make coverage | Run tests with coverage |
| make check | Lint + typecheck + test |
| make clean | Remove generated files |
Testing
Tests run under vitest (happy-dom). Because the layer uses explicit imports, Nuxt's virtual #imports is shimmed for the test environment (tests/mocks/imports.ts — real Vue/VueUse + stubbed Nuxt composables), and #test is aliased in vitest.config.ts. Component tests mount with @vue/test-utils using the shared stubs in tests/stubs/ (coreStubs / per-feature data maps).
Companion modules (in progress)
Theming, i18n, auth, telemetry, and icons are being extracted into standalone modules — @zoobz-io/untheme, @zoobz-io/rosetta, @zoobz-io/rampart, @zoobz-io/crucible, @zoobz-io/iconic. Until they land, the components that depend on them (auth / theme / locale controls, the icon sprite) are not wired up.
Contributing
- Conventional commits:
feat:,fix:,docs:,test:,refactor:,chore: - Tests required for all new code
make check(lint + typecheck + test) must pass before opening a PR- Add a changeset (
pnpm changeset) in any PR that should ship a release - Node 22, pnpm 9.10.0
Releasing
Versioning runs on changesets. Each
PR that changes published behavior carries a changeset (pnpm changeset, then
commit the generated file); pre-1.0, minor = feature, patch = fix.
Releases are cut manually via the Release workflow (Actions tab → Run
workflow, or gh workflow run Release). It applies every pending changeset —
bumping the version, rewriting CHANGELOG.md, committing the bump, publishing to
npm, and pushing the tag. Publishing uses npm OIDC trusted publishing (no
NPM_TOKEN; provenance is attested), so the package must have a trusted
publisher configured on npmjs pointing at this repo's Release workflow.
License
MIT
