Visual JSON Editor
A dependency-free, (optionally) schema-guided JSON editor that renders a visual form. It supports nested objects/arrays, optional JSON Schema guidance, and a default DOM renderer with customizable styling.
Check out the playground where you can test with your own JSON schema's. Or check out the demo with some provided schemas. There is also a demo of a vue-based implementation.
jsDelivr (latest):
- ESM: https://cdn.jsdelivr.net/gh/celerex/json-editor@master/dist/editor.esm.js
- UMD (min): https://cdn.jsdelivr.net/gh/celerex/json-editor@master/dist/editor.umd.min.js
- CSS: https://cdn.jsdelivr.net/gh/celerex/json-editor@master/dist/editor.css
An example from a tool where it is incorporated into the theme:
Quick Start
<link rel="stylesheet" href="./src/editor.css"/> <div id="editor"></div> <script type="module"> import { createDomEditor } from "./src/editor.js"; const schema = { title: "Profile", type: "object", properties: { name: { type: "string" }, status: { enum: ["active", "paused"] }, tags: { type: "array", items: { type: "string" } } }, required: ["name"] } const editor = createDomEditor( { container: document.querySelector("#editor"), value: {}, schema, defaultCollapsed: true, onChange(next) { console.log("json changed", next) } } ) // later // editor.setValue({ name: "Ada" }) // editor.setSchema(nextSchema) // editor.destroy() </script>
API
createDomEditor(options)
Creates a DOM renderer and mounts it into container.
Options:
container(required): DOM elementvalue: initial JSON valueschema: optional JSON Schemalenient(defaultfalse): allow adding keys not in schemadefaultCollapsed(defaulttrue): start sections collapsed (root stays open)onChange(value): called after each editonUpdate(value): same asonChange, for external syncmessages: override message strings (for i18n)messageResolver(key, params): custom message functionclasses: additional class names per element type
Returns an editor instance with:
setValue(value)setSchema(schema)subscribe(fn)(returns unsubscribe)destroy()
createEditorCore(options)
Creates a core editor and emits UI trees via onRender. Use this if you want to build your own renderer.
Options are the same as above plus:
onRender(tree)— receives the UI tree on each update
Styling
CSS Variables (easy theming)
Default styles are in src/editor.css. Override any --je-* variable to theme.
:root { --je-page-bg: #12110f; --je-panel-bg: #1b1916; --je-text-primary: #f2e9dc; --je-input-bg: #211e1a; --je-input-border: #3a332c; --je-tooltip-bg: #0f0e0c; }
Custom Classes
You can add classes per element in createDomEditor({ classes }):
createDomEditor({ container, value: {}, schema, classes: { field: "my-field", input: "my-input", remove: "my-remove", row: "my-row", arrayRow: "my-array-row", docIcon: "my-doc" } })
Supported keys:
field, object, array, row, arrayRow, label, docIcon, input, remove, move, header, title, badge, description, body, addRow, addButton, key, value, warning, error, toggle
Custom Components / Renderers
This project is framework-agnostic by design. The core editor generates a UI-level tree and node methods (update/add/remove/etc.).
- Use
createEditorCore({ onRender })to receive that tree. - Build your own renderer that maps nodes to your UI library.
Vue Example (conceptual)
import { createEditorCore } from "./src/editor.js" const editor = createEditorCore({ value: {}, schema, onRender(tree) { // store tree in Vue state appState.tree = tree } }) // In Vue component, render based on `appState.tree` // Use node methods: node.update(value), node.addItem(option), node.toggle(), etc.
Plugging Custom Field Controls
In a custom renderer, map by node.kind:
field→ input controlobject→ collapsible sectionarray→ list of items + add controls
Each node provides methods like update, remove, addItem, addField, and toggle.
UI Hints
This editor supports a simple UI hint for multiline text:
"ui:widget": "textarea"
Example:
{
"type": "string",
"title": "Notes",
"ui:widget": "textarea"
}Validation Messages (i18n)
You can override validation copy using messages or messageResolver:
createDomEditor({ container, value: {}, schema, messages: { invalid: "Ungültig", required: "Fehlend: {fields}", min: "Muss ≥ {min} sein" } })
Or use a function:
createDomEditor({ container, value: {}, schema, messageResolver(key, params) { if (key === "min") return `At least ${params.min}` return "Invalid" } })
Demo
Open demo.html using a local server:
python3 -m http.server 8080
Then visit: http://localhost:8080/demo.html
Hidden Shortcut
Hold Shift while clicking a section toggle to expand/collapse the entire subtree.
Build
Build ESM/UMD bundles and copy CSS into dist/:
npm install npm run build
Outputs:
dist/editor.esm.jsdist/editor.umd.jsdist/editor.umd.min.jsdist/editor.css
UMD Usage (No Module System)
<link rel="stylesheet" href="./dist/editor.css"/> <div id="editor"></div> <script src="./dist/editor.umd.min.js"></script> <script> const schema = { type: "object", properties: { name: { type: "string" } } } const editor = JsonEditor.createDomEditor({ container: document.querySelector("#editor"), value: {}, schema }) </script>
