MKSingh

MKSingh

Docs
JSONForm Renderers

Quick Start

A complete set of ShadCN-based renderers for JSONForms — schema-driven forms without writing repetitive form code.

JSONForms generates forms from a JSON Schema and a UI Schema. Instead of hand-coding every input, you describe your data model and layout declaratively, and the renderer handles the rest.

This registry provides a drop-in renderer set built entirely on ShadCN components — no extra design system, no style conflicts. The code lands directly in your project so you own and customize it.

Installation

Install everything at once:

npx shadcn@latest add https://mksingh.dev/r/jsonforms-renderers.json

This installs all renderers, layouts, and cells under components/forms/renderers/, the utility helpers under components/forms/utils.ts, and pulls in the required ShadCN components automatically.

Install individually

The utils helper is also available standalone:

npx shadcn@latest add https://mksingh.dev/r/jsonforms-utils.json

Every renderer is also available as a standalone registry item:

npx shadcn@latest add https://mksingh.dev/r/jsonforms-boolean-control.json
npx shadcn@latest add https://mksingh.dev/r/jsonforms-array-layout.json
npx shadcn@latest add https://mksingh.dev/r/jsonforms-enum-cell.json

Quick start

app/my-form.tsx
'use client';

import { useState } from 'react';
import { JsonForms } from '@jsonforms/react';

import { jsonFormsCells, jsonFormsRenderers } from '~/components/forms/renderers'; 

const schema = {
    type: 'object',
    properties: {
        name: { type: 'string', minLength: 1 },
        age:  { type: 'integer', minimum: 0 },
    },
    required: ['name'],
};

const uischema = {
    type: 'VerticalLayout',
    elements: [
        { type: 'Control', scope: '#/properties/name' },
        { type: 'Control', scope: '#/properties/age' },
    ],
};

export default function MyForm() {
    const [data, setData] = useState({});

    return (
        <JsonForms
            schema={schema}
            uischema={uischema}
            data={data}
            renderers={jsonFormsRenderers} 
            cells={jsonFormsCells}         
            onChange={({ data }) => setData(data)}
        />
    );
}

How it works

JSONForms uses a tester/renderer system. When rendering a field, it runs all registered testers against the current schema node and picks the renderer with the highest score.

This means:

  • Renderers compose without configuration — the right one is selected automatically.
  • You can override any renderer by registering a new one with a higher rank.
  • Adding a renderer is additive — it doesn't break existing ones.

Validation & errors

Validation runs on every change via AJV. Errors are hidden by default. Control when they appear using config.shouldValidate:

ModeBehaviour
onBlurShow errors after each field loses focus
onSubmitShow all errors at once (set this on form submission)
app/my-form.tsx
<JsonForms
    // ...
    config={{ shouldValidate: 'onBlur' }}
/>

For submit-gated validation, toggle the mode on submission:

app/my-form.tsx
const [shouldValidate, setShouldValidate] = useState<ShouldValidate | undefined>(undefined);

const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    setShouldValidate('onSubmit');
};

<JsonForms
    // ...
    config={{ shouldValidate }}
/>

Custom error messages

Pass a translateError function via the i18n prop to replace AJV's default messages with human-readable ones. The function receives the AJV error object and should return a string, or an empty string to fall back to the default.

Both translateError and ShouldValidate ship in components/forms/utils.ts:

app/my-form.tsx
import { translateError, type ShouldValidate } from '~/components/forms/utils'; 

<JsonForms
    // ...
    i18n={{ translateError }}
    config={{ shouldValidate }}
/>

Add errorMessage to your JSON Schema properties to provide per-keyword messages:

{
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "minLength": 2,
            "errorMessage": {
                "minLength": "Name must be at least 2 characters."
            }
        }
    },
    "required": ["name"]
}

Each renderer displays only the first error line (errors.split('\n')[0]), so a single, focused message is shown even when multiple validations fail simultaneously.

Full example with submit / reset

app/my-form.tsx
'use client';

import { useState } from 'react';
import { JsonForms } from '@jsonforms/react';
import { Button } from '~/components/ui/button';
import { jsonFormsCells, jsonFormsRenderers } from '~/components/forms/renderers';
import { translateError, type ShouldValidate } from '~/components/forms/utils'; 

const schema = { /* ... */ };
const uischema = { /* ... */ };

export default function MyForm() {
    const [data, setData] = useState({});
    const [shouldValidate, setShouldValidate] = useState<ShouldValidate | undefined>(undefined);

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        setShouldValidate('onSubmit'); 
    };

    const handleReset = () => {
        setData({});
        setShouldValidate(undefined);
    };

    return (
        <form onSubmit={handleSubmit}>
            <JsonForms
                schema={schema}
                uischema={uischema}
                data={data}
                renderers={jsonFormsRenderers}
                cells={jsonFormsCells}
                i18n={{ translateError }}
                config={{ shouldValidate }} 
                onChange={({ data }) => setData(data)}
            />
            <div className="flex gap-2 mt-4 justify-end">
                <Button variant="secondary" type="button" onClick={handleReset}>Reset</Button>
                <Button type="submit">Submit</Button>
            </div>
        </form>
    );
}

What's included

On this page