npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ficsysfr/jsonforms_builder

v2.0.2

Published

JSONForms renderers for Vue 3 built on Nuxt UI & Tailwind CSS

Readme

JSON Forms Builder

CI Docs llms.txt MCP codecov NPM Version NPM Downloads License

JSONForms Builder banner

JSONForms renderers for Vue 3, built on Nuxt UI 4 and Tailwind CSS 4 — plus a visual builder to compose { schema, uischema } without writing JSON by hand.

v2 — stack change. v1 was based on Quasar. v2 renders with Nuxt UI U* components and therefore inherits the host app theme automatically. The v1-quasar branch keeps the old implementation; @tacxou/[email protected] remains installable.

npm scope. Starting with 2.0.2, the maintained packages live under @ficsysfr. The former @tacxou v2 packages remain installable but are frozen.

Installation

yarn add @ficsysfr/jsonforms_builder @jsonforms/core @jsonforms/vue @nuxt/ui

@nuxt/ui, @jsonforms/core, @jsonforms/vue, and vue are peerDependencies: the library does not ship any Nuxt UI components; it imports them from the host app installation.

Usage

<template lang="pug">
  json-forms(
    :data="data"
    :schema="schema"
    :uischema="uischema"
    :renderers="renderers"
    validation-mode="ValidateAndShow"
    @change="onChange"
  )
</template>

<script setup lang="ts">
import { JsonForms } from '@jsonforms/vue'
import { nuxtUiRenderers } from '@ficsysfr/jsonforms_builder'

const renderers = Object.freeze(nuxtUiRenderers)
</script>
  • nuxtUiRenderers — controls, layouts, and additional elements.
  • allRenderers — the same, plus the rich-text editor (UEditor).

Visual builder

<template lang="pug">
  form-builder(v-model="definition")
</template>

<script setup lang="ts">
import { FormBuilder, type FormDefinition } from '@ficsysfr/jsonforms_builder'

const definition = ref<Partial<FormDefinition>>({})
</script>

Palette, drag-and-drop reorderable tree, property inspector, live preview, and JSON export. Raw JSON editing is left to the host app (Monaco, CodeMirror…).

Renderers

| Schema / option | Nuxt UI component | |---|---| | string | UInput | | string + options.multi | UTextarea | | string + format: password | UInput + visibility toggle | | object / string + options.wysiwyg | UEditor (JSON or HTML via contentType) | | WYSIWYG images | upload dropzone, drag-resize, bubble replace/delete |

WYSIWYG options (options.wysiwyg: true):

| Option | Role | |---|---| | contentType | 'json' | 'html' (else inferred from schema) | | toolbar | Nuxt UI toolbar groups, or false to hide | | density | 'compact' | 'comfortable' | 'prose' | | minHeight / padding / blockSpacing / editorClass | spacing overrides | | debounce | onChange debounce in ms (default 300) | | image | false to disable, or { upload, accept, maxSize, resize, bubble, … } | | extensions / handlers | extra TipTap extensions / Nuxt UI handlers |

options: {
  wysiwyg: true,
  contentType: 'html',
  density: 'comfortable',
  image: {
    upload: async (file) => uploadedUrl,
    maxSize: 5 * 1024 * 1024,
    resize: { minWidth: 80, alwaysPreserveAspectRatio: true },
  },
}

| string + options.format: pin | UPinInput | | string + format: color | UColorPicker in a popover + text input | | string + format: data-url | UFileUpload | | number / integer | UInputNumber | | number + options.slider | USlider | | number + options.format: rating | UInputRating | | boolean | UCheckbox (USwitch via options.toggle) | | enum | USelectMenu | | enum + options.format: select | USelect (no search) | | enum + options.format: radio | URadioGroup | | string + options.api | UInputMenu (remote search) | | format: date / date-time / time | UInputDate / UInputTime | | format: date + options.format: calendar | expanded UCalendar | | array of string + options.format: tags | UInputTags | | array | repeatable cards (add, reorder, remove) | | oneOf | variant selector + sub-form | | Group | titled UCard | | Categorization | UTabs (UStepper via options.variant: "stepper") | | Label | heading + USeparator |

Customization

Two levels, from broadest to most specific:

// 1. Global theme, injected once for the whole tree.
provide('styles', { control: { input: 'font-mono' } })
// 2. Per element, via uischema options — `<slot>` is the target component.
{
  "type": "Control",
  "scope": "#/properties/name",
  "options": { "input": { "size": "lg", "ui": { "base": "tracking-wide" } } }
}

Integration

Declare the library to Tailwind (required)

Tailwind 4 generates utilities by scanning project sources and ignores everything outside its root — including node_modules. Without the line below, classes used by the renderers appear in the DOM but map to no CSS rule: selection borders and backgrounds disappear, and spacing shifts.

@import "tailwindcss";
@import "@nuxt/ui";

@source "../node_modules/@ficsysfr/jsonforms_builder/dist";

If your brand theme is declared in an @theme block, use @theme static. Tailwind prunes variables that no source references directly, and a color consumed only by Nuxt UI's generated CSS (--ui-primary: var(--color-my-color-500)) silently falls away — the theme then reverts to default colors.

Nuxt

export default defineNuxtConfig({
  modules: ['@nuxt/ui'],
  vite: {
    optimizeDeps: {
      // The library keeps imports into `@nuxt/ui` SFCs: esbuild's pre-bundler
      // cannot compile them, so exclude it.
      exclude: ['@ficsysfr/jsonforms_builder'],
      // `ajv` is CommonJS. Without pre-bundling, its default export is not exposed
      // and `@jsonforms/core` fails on import.
      include: ['ajv', 'ajv-formats', '@jsonforms/core', '@jsonforms/vue'],
    },
  },
})

When updating the library — if the browser throws does not provide an export named '…' on @jsonforms/vue or @jsonforms/core, Vite's pre-bundle is stale: a new version may import from an already optimized dependency an export it did not import before, and Vite does not always invalidate its cache. A restart with a cleared cache is enough:

rm -rf node_modules/.vite && vite --force

Vue + Vite (without Nuxt)

import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [vue(), ui({ colorMode: true })],
})

The WYSIWYG renderer also requires deduplicating ProseMirror — its plugins are identified by object identity, and two copies in the dependency tree throw Adding different instances of a keyed plugin:

resolve: {
  dedupe: ['@tiptap/core', '@tiptap/pm', '@tiptap/vue-3', 'prosemirror-state', 'prosemirror-view', 'prosemirror-model'],
}

See playground/vite.config.ts for a full commented configuration (including the #imports stubs required by @nuxt/icon outside Nuxt).

Known upstream default — Cannot read properties of null (reading 'subTree')

@vueuse/core 14.4.0 (latest as of writing, pulled by Nuxt UI) contains in onClickOutside:

function hasMultipleRoots(target) {
  const vm = toValue(target)
  return vm && vm.$.subTree.shapeFlag === 16   // `vm` is guarded, `vm.$` is not
}

After a component unmounts, vm.$ is null: any later click that reaches a surviving listener throws. The triggering case is an open menu whose selection click unmounts the subtree — typically a oneOf variant change, or removing an array row.

The workaround applied in the affected renderers is to defer the unmounting mutation by one nextTick, so the menu can finish closing first. Reproduce this in the host app if it unmounts subtrees itself from an @update:model-value handler.

Development

This project runs on Node.js ≥ 22 with Yarn (Classic 1.x, see packageManager) as the sole package manager: yarn.lock is authoritative — do not install with npm or pnpm.

yarn install
yarn start:dev        # playground alone + mock API (http://localhost:5174)
yarn docs:dev         # VitePress + playground (http://localhost:5173/playground)
yarn docs:build       # site docs unique pour GitHub Pages
yarn docs:preview     # prévisualiser le build docs
yarn build            # library build (es + cjs + declarations)
yarn package          # audited library + MCP tarballs → .artifacts/npm
yarn test             # Vitest suite
yarn test:watch       # same, watch mode
yarn test:coverage    # v8 coverage → ./coverage/lcov.info
yarn lint             # Biome: lint + format check
yarn typecheck        # TypeScript validation without emitting files
yarn test:scripts     # release/changelog/package tooling
yarn changelog:check  # CHANGELOG.md matches changelog/X.Y.Z.md
yarn lint:fix         # apply safe fixes and reformat

make help lists the equivalent Make targets (make dev, make build, make test…).

Release

Releases use one manually dispatched GitHub Actions workflow. Versioned changelog/X.Y.Z.md files generate CHANGELOG.md and the exact GitHub Release body; git tags stay bare (2.0.2, no v prefix).

make release VERSION=2.0.2 CHANNEL=latest WATCH=1
make release VERSION=2.1.0-rc.1 CHANNEL=next WATCH=1

The explicit version makes retries idempotent. The workflow runs the full CI, synchronizes both package manifests, validates and publishes the exact .tgz files, verifies registry integrity, pushes the release commit and tag, then creates the GitHub Release with both tarballs and SHA256SUMS.txt attached.

Publishing uses npm Trusted Publishing (OIDC) from the npm GitHub environment. No permanent npm write token is required; NPM_BOOTSTRAP_TOKEN is accepted only while creating the packages for the first time and must then be removed.

Documentation site (GitHub Pages)

The VitePress site in docs/ is published to GitHub Pages. The interactive playground is compiled into the same site (mounted on /playground, hash routes #/?example=… / #/builder) — no separate iframe or /play/ SPA.

In the playground sidebar:

  • Documentation — Nuxt UI control showcases (nuxt-*, control options, …)
  • Examples — JSONForms demos, layouts, compositions, and edge cases

Enable Pages in the repository settings (Settings → Pages → Source: GitHub Actions). The workflow .github/workflows/deploy-docs.yml builds and deploys on every push to main.

AI agents

Machine-readable docs and an MCP server for Cursor / Claude:

Linting and formatting are handled by Biome (biome.jsonc), replacing ESLint and Prettier. Two limits come from the Vue + Pug stack:

  • Biome only analyzes an SFC's <script> block, never the <template>. The noUnusedVariables and noUnusedImports rules are therefore disabled on .vue files, where any binding consumed by the template would look unused.
  • Formatting does not touch Pug templates, which are left to .editorconfig.

Alt