shadcn/ui Renderer

Render JSONForms with shadcn/ui components

Overview

The @fragno-dev/jsonforms-shadcn-renderers package provides a JSONForms renderer built for shadcn/ui components. Use it to render forms from the Form fragment with a consistent shadcn/ui look and feel.

Live demo
See the Form fragment combined with our shadcn/ui renderer in action

Because shadcn/ui inherently vendors components, we rely on the bundler to resolve component imports in your project.

Installation

Install the renderer package alongside JSONForms React:

npm install @fragno-dev/jsonforms-shadcn-renderers @jsonforms/react

Required ShadCN Components

The renderers use these shadcn/ui components. Add them to your project:

npx shadcn@latest add button \
    calendar \
    card \
    checkbox \
    field \
    input \
    label \
    popover \
    radio-group \
    select \
    slider \
    switch \
    tabs \
    textarea

Configuration

Vite

The renderers import shadcn components from @/components/ui/*. Configure Vite to resolve these imports:

vite.config.ts
import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "@/components": path.resolve(__dirname, "./src/components"),
      "@/lib": path.resolve(__dirname, "./src/lib"),
    },
  },
  ssr: {
    noExternal: ["@fragno-dev/jsonforms-shadcn-renderers"],
  },
});

Tailwind CSS

Include the package in your Tailwind sources so the renderer classes are included in your build:

app.css
@source "../node_modules/@fragno-dev/jsonforms-shadcn-renderers";

See the Tailwind docs on source detection for more details.

Usage

Use the shadcnRenderers and shadcnCells with the JSONForms component:

components/dynamic-form.tsx
import { useState } from "react";
import { JsonForms } from "@jsonforms/react";
import { shadcnRenderers, shadcnCells } from "@fragno-dev/jsonforms-shadcn-renderers";

interface DynamicFormProps {
  schema: Record<string, unknown>;
  uiSchema: Record<string, unknown>;
  onSubmit: (data: Record<string, unknown>) => void;
}

export function DynamicForm({ schema, uiSchema, onSubmit }: DynamicFormProps) {
  const [data, setData] = useState({});

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        onSubmit(data);
      }}
    >
      <JsonForms
        schema={schema}
        uischema={uiSchema}
        data={data}
        renderers={shadcnRenderers}
        cells={shadcnCells}
        onChange={({ data }) => setData(data)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

With the Form Fragment

Combine the renderer with the Form fragment client:

components/contact-form.tsx
import { useState } from "react";
import { JsonForms } from "@jsonforms/react";
import { shadcnRenderers, shadcnCells } from "@fragno-dev/jsonforms-shadcn-renderers";
import { formsClient } from "@/lib/forms-client";

export function ContactForm() {
  const [formData, setFormData] = useState({});
  const { data: form, loading: formLoading } = formsClient.useForm({
    pathParams: { slug: "contact" },
  });
  const { mutate: submit, loading: submitting } = formsClient.useSubmitForm();

  if (formLoading || !form) return <div>Loading...</div>;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await submit({
      pathParams: { slug: "contact" },
      body: { data: formData },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <JsonForms
        schema={form.dataSchema}
        uischema={form.uiSchema}
        data={formData}
        renderers={shadcnRenderers}
        cells={shadcnCells}
        onChange={({ data }) => setFormData(data ?? {})}
      />
      <button type="submit" disabled={submitting}>
        {submitting ? "Submitting..." : "Submit"}
      </button>
    </form>
  );
}

Supported Features

JSONforms primitives supported by this renderer.

Controls

FeatureStatus
Boolean (checkbox)
Boolean (toggle/switch)
Text
Textarea (multi-line)
Integer
Number
Slider
Date
Time
DateTime
Enum (select)
Enum (radio)
OneOf Enum (select)
OneOf Enum (radio)
Object (nested)
Array (table)
Array (expandable panels)
AllOf
AnyOf
OneOf (polymorphic)
Enum Array (multi-select)
AnyOf String/Enum
Native inputs

Layouts

FeatureStatus
Vertical
Horizontal
Group
Categorization (tabs)
Categorization (stepper)
Array Layout

Additional Renderers

FeatureStatus
Label
ListWithDetail

Cells

FeatureStatus
Boolean
Boolean Toggle
Text
Integer
Number
Number Format
Date
Time
DateTime
Enum
Enum Radio
OneOf Enum
Slider