@gxp-dev/mod-ui

JSON-driven Vue 3 module UI component library for the GxP platform. Provides shadcn-vue primitives, a templating engine (IndexPage / ShowPage), card/element renderers, and a FormBuilder.
13,674
Install
npm install @gxp-dev/mod-ui
Latest Version:2.0.41
Vue:3
License:MIT
Last Updated:Aug 20, 2026
Links: npm
Maintainer: keepevets

@gxp-dev/mod-ui

JSON-driven Vue 3 admin component library for the GxP platform. Provides shadcn-vue primitives, props-based composition components, a templating engine (IndexPage / ShowPage), and a FormBuilder ecosystem. Designed to be a drop-in replacement for the legacy Components/Mod and Components/Mod/v2 systems in the experience portal.

Status: 2.0.0-alpha.0 — feature-complete for rendering and JSON drop-in. 1410 passing tests across 124 files. See CHANGELOG.md for details and known limitations.

Two APIs in one library

Props-based (use any composition directly, no JSON, no schema):

<script setup lang="ts">
import { TextInput, DatePicker, Button } from "@gxp-dev/mod-ui"
</script>

<template>
  <TextInput v-model="firstName" label="First name" required />
  <DatePicker v-model="startDate" label="Start date" />
  <Button variant="default" @click="save">Save</Button>
</template>

JSON-driven (pass a config, the engine resolves and renders):

<script setup lang="ts">
import { ShowPage } from "@gxp-dev/mod-ui"
import type { ShowPageConfig } from "@gxp-dev/mod-ui"

const config: ShowPageConfig = {
  title: "User",
  tabsList: [
    {
      title: "Details",
      tabId: "details",
      cards: [
        {
          // v1 (`fields_list`) is auto-translated at the boundary.
          // Native v2: `cardType: "element-list"`.
          type: "fields_list",
          fieldsList: [
            { type: "text", name: "first_name", label: "First name" },
            { type: "email", name: "email", label: "Email" },
          ],
        },
      ],
    },
  ],
  updateConfig: {
    update: { route: "api.users.update", method: "patch" },
  },
}
</script>

<template>
  <ShowPage :config="config" :item="user" />
</template>

The same ShowPage accepts both v1 and v2 schemas — the adapter detects the shape at the boundary.

What's in the box

Layer What lives here Purpose
components/ui/ 30 shadcn-vue primitives (Button, Input, Dialog, Select, FieldFrame, …) Atom-level building blocks built on reka-ui.
components/compositions/elements/ 79 props-based element compositions (TextInput, DatePicker, ColorPicker, RichTextEditor, ChartDisplay, …) Self-contained Vue components with defineModel('modelValue'). Mountable in any app.
components/compositions/cards/ 7 card compositions (ElementList, DataTable, CardList, TabList, Info, GridView, ElementDisplay) Container compositions that render resolved children.
pages/ IndexPage, ShowPage Top-level templating consumers; take a JSON config.
definitions/ TypeScript schema source of truth (no Vue) Defaults, EditConfig, and interfaces per element/card.
engine/ ElementRenderer, CardRenderer, loaders, legacyPropRemap JSON → props dispatch. Resolves type, calls useFormData, strips schema-only keys, forwards as v-bind.
adapters/ v1 ↔ v2 schema translation, normalisePageConfig Drop-in support for legacy v1 JSON.
stores/ 9 Pinia stores Per-form data binding; per-builder schema state.

Architectural principles

  1. Compositions take normal Vue props. Every renderer under components/compositions/ is a self-contained Vue component with defineModel<T>('modelValue'), typed individual props, and standard events. Each is directly mountable in any Vue app without any knowledge of the JSON schema or Pinia.
  2. The engine is the JSON ↔ props bridge. ElementRenderer and CardRenderer look up the matching composition by element.type / card.cardType, call useFormData(element.name) for the form binding, strip schema-only keys, and forward the rest as v-bind props. Compositions never import schema types.
  3. TypeScript definitions are the schema source. Each element category has a single dot-notation discriminator ("input.text", "picker.date-picker", …) — no separate style field. The runtime registry and adapters derive from these definitions.
  4. Tree-shakeable. Direct deep imports work alongside the barrel.
  5. Code-split. The engine resolves the composition path via import.meta.glob + defineAsyncComponent — a ShowPage only downloads the elements it actually renders.
  6. No mdbootstrap, no Bootstrap CSS. Pure Tailwind v4 + CSS variables. reka-ui for a11y primitives.
  7. Themes are CSS variables. Override any token in :root (or a scoped class like .kiosk) and the whole library retunes.

Install

pnpm add @gxp-dev/mod-ui

vue, pinia, and reka-ui are peer dependencies — modern package managers (pnpm 9+, npm 7+, yarn 3+) auto-install them, so the single command above is usually enough. If your manager warns about unresolved peers, add them explicitly: pnpm add vue pinia reka-ui.

Styles

Import the library's stylesheet once in your app entry (e.g. main.ts or app.js):

import "@gxp-dev/mod-ui/styles"

This loads the Tailwind theme — CSS custom properties for color, radius, shadow, etc. Without it every component will be unstyled.

Themes

The library ships three built-in themes:

File Class Description
(default, included in styles) — Light theme
@gxp-dev/mod-ui/styles/themes/dark.css .dark Dark theme
@gxp-dev/mod-ui/styles/themes/kiosk.css .kiosk High-contrast kiosk theme

To use dark or kiosk mode, import the extra stylesheet and add the class to a parent element:

import "@gxp-dev/mod-ui/styles"
import "@gxp-dev/mod-ui/styles/themes/dark.css"
<body class="dark">...</body>

To override individual design tokens:

:root {
  --primary: oklch(0.5 0.2 270);
  --radius: 1rem;
}

Develop

pnpm install
pnpm dev            # vite dev server (rarely used; storybook is the demo)
pnpm storybook      # storybook on :6006
pnpm test           # vitest watch
pnpm test:run       # one-shot
pnpm typecheck      # vue-tsc --noEmit
pnpm build          # library build (dist/)

Migrating from Components/Mod (v1) or Components/Mod/v2

The adapters/ module accepts both shapes and emits the v2-internal structure. ShowPage / IndexPage apply the adapter automatically at the page boundary, so existing v1 JSON renders without code changes in the consumer.

For direct schema migration without rendering:

import { v1ToV2, v2ToV1, normalisePageConfig } from "@gxp-dev/mod-ui"

v2 schema notes

  • Elements use a single type field in dot notation: "input.text", "picker.date-picker", "option-select.select", etc. The separate style field that v2 used early on is gone (it collided with HTML style attribute fallthrough).
  • A small legacyPropRemap shim handles known legacy v2 key renames (selectOptions → nestedElements, triggerButtonLabel → triggerLabel, etc.) and variant aliases (variant: "primary" → "default") automatically at the engine boundary.

Related Packages

@gxp-dev/app-ui

Vue 3 UI component library for GXP platform applications, built on shadcn-vue, r...

7,666
fuxsto-design

Monochrome zinc, Tailwind CSS v4 component library for Vue 3 (fuxsto-ui)

2,089
@inksightdev/ui

InkSight UI — reusable Vue 3 component library (ShadCN + Tailwind)

3,879
z_vue3

ZUI — Vue 3.5 组件库,基于 shadcn-vue + Tailwind CSS v4,42 组件 + 7 函数式 API...

2,648
vue3-tailwind-elements

A light library of components based on Tailwind Elements and Tailwind for Vuejs...

2,486 2

Version History

Version Released License
2.0.41 MIT
2.0.40 MIT
2.0.39 MIT
2.0.38 MIT
2.0.37 MIT