MonacoJsonInput
Edits a JSON value in a Monaco editor, with JSON Schema checks and error markers that block save.
Installation
pnpm dlx shadcn@latest add @chelone/monaco<MonacoJsonInput> edits a JSON value in the Monaco editor. It is an add-on:
it is not in the @chelone/react barrel, and Monaco is an optional peer
dependency. See Installing the add-on.
When to use it
Use it for a field that holds a JSON document a user writes by hand, such as a settings object, a webhook payload template or a feature-flag config. You get syntax highlighting, completion from a JSON Schema, a Format button and error markers.
For a short value, prefer a plain input such as TextInput. To show a JSON value without editing it, use MonacoJsonField or JsonField.
A schema example
Pass a JSON Schema in schema. Monaco offers completions from it and marks
values that do not match.
import { Edit } from "@/components/admin";
import { MonacoJsonInput } from "@/components/monaco";
const settingsSchema = {
type: "object",
properties: {
theme: { enum: ["light", "dark"] },
retries: { type: "integer", minimum: 0 },
},
required: ["theme"],
};
export const IntegrationEdit = () => (
<Edit>
<MonacoJsonInput source="settings" schema={settingsSchema} height={240} />
</Edit>
);Use schemaUri to have Monaco fetch the schema from a URL instead. schema
wins when both are set. A schema applies to the object shape only.
String and object values
The input reads the shape of the value when it first renders and keeps it:
- An object or array, or an empty value, is edited as JSON. The form gets the
parsed value, so
{ "theme": "dark" }is stored as an object. - A string is edited as typed and written back as a string. A column that holds JSON text stays text.
While the text is not valid JSON, the form keeps the last valid value.
Set allowComments to accept comments and trailing commas in the text of an
object value. They are stripped before the text is parsed, so the stored value
never holds them. A string value is written back exactly as typed, comments
included, so leave allowComments off for a column that other code parses as
strict JSON.
Validation
A Monaco error marker blocks save. That covers two kinds of error:
- A syntax error, such as a missing comma or an unclosed brace.
- A schema error, such as a value of the wrong type or a missing required property.
The message shows under the editor like any other input error. Warnings do not
block save. The input also takes the usual validate prop, which runs after
these checks.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| allowComments | boolean | — | Tolerate comments and trailing commas in the text. |
| alwaysOn | boolean | — | As a `<FilterForm>`/`<FilterButton>` filter: always shown, never in the add-filter menu. Read off the element by the filter layer, never by the input. |
| autoHeight | boolean | — | Grow with the content between `minHeight` and `maxHeight`. Defaults to false. |
| className | string | — | Class for the field wrapper. |
| defaultValue | unknown | — | Input-level default, ra precedence (seeds only into an untouched hole). |
| disabled | boolean | — | Native disabled state. |
| editorClassName | string | — | Class for the editor frame. |
| format | (value: unknown) => unknown | — | Read transform (raw form value → input value). |
| height | number | — | Editor height in pixels when `autoHeight` is false. Defaults to 300. |
| helperText | ReactNode | — | Helper text rendered below the input; a string is translated. |
| id | string | — | Explicit element id; a generated `useId` value is used otherwise. |
| isRequired | boolean | — | Forces the required marker; ORed with `required()` detection on `validate`. |
| label | ReactNode | — | The input label. A string is translated; a ReactNode renders as-is; `false` (or `""`) renders no label. |
| maxHeight | number | — | Tallest the editor grows to when `autoHeight` is on. Defaults to 600. |
| minHeight | number | — | Shortest the editor gets when `autoHeight` is on. Defaults to 120. |
| monacoOptions | IStandaloneEditorConstructionOptions | — | Merged over the editor defaults, so it can override any of them. |
| name | string | — | Legacy field-name alias (ra parity); the wiring uses `source`. |
| onBlur | (event: unknown) => void | — | Chained AFTER the blur is recorded. |
| onChange | (eventOrValue: unknown) => void | — | Chained AFTER the parsed value is written to the form. |
| parse | (value: unknown) => unknown | — | Write transform (input value → stored value). |
| readOnly | boolean | — | Native read-only state. |
| resource | string | — | Override the ambient resource (used to resolve the default label key). |
| schema | object | — | A JSON Schema the value is checked against. Object shape only. |
| schemaUri | string | — | A schema Monaco fetches from this URL. Ignored when `schema` is set. |
| showFormatButton | boolean | — | Show the Format button. Defaults to true. |
| source | string | — | The field path RELATIVE to the ambient source scope. |
| type | string | — | The DOM input `type` (text inputs default to `"text"`). |
| validate | Validator | readonly Validator[] | — | ra-contract validators — composed, first error wins. |
Installing the add-on
There are two ways in. Both give you <MonacoJsonInput> and
<MonacoJsonField>.
The registry copies the components into your project, and the code samples on
these pages import them from @/components/monaco:
npx shadcn add @chelone/monacoIt installs monaco-editor and the binding library for you.
Or import from the npm subpath, and install the optional peers yourself. For
React that is @monaco-editor/react and monaco-editor:
pnpm add @monaco-editor/react monaco-editorimport { MonacoJsonField, MonacoJsonInput } from "@chelone/react/monaco";For Vue it is @guolao/vue-monaco-editor and monaco-editor:
pnpm add @guolao/vue-monaco-editor monaco-editorimport { MonacoJsonField, MonacoJsonInput } from "@chelone/vue/monaco";The main @chelone/react and @chelone/vue entries never import Monaco, so an
app that does not use the add-on does not need these packages.
Loading Monaco
- Monaco is bundled with your app, in a lazy chunk. Nothing reaches the base
bundle until the first
<MonacoJsonInput>or<MonacoJsonField>renders, and a skeleton holds the place until then. - It loads no script from a CDN.
- The editor theme follows the Chelone theme provider: light in light mode, dark in dark mode.
- Workers run on the main thread unless the app sets
self.MonacoEnvironment.getWorker. Without it Monaco logs a console warning and still works. Set it once, before the first render, for a smoother editor.
With Vite, import the two workers with ?worker and return the one that matches
the label:
import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import JsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
self.MonacoEnvironment = {
getWorker(_workerId, label) {
return label === "json" ? new JsonWorker() : new EditorWorker();
},
};Put it in your entry file, ahead of the render call.
Vue
@chelone/vue has the same component, built on @guolao/vue-monaco-editor.
These props are written differently:
classstyles the field wrapper, where React hasclassName.editor-classstyles the editor frame, where React haseditorClassName.labelis a string, orfalseto hide it, andhelper-textis a string.- It has no
id,resource,isRequired,format,parseoralwaysOnprop, and emits no events. The label is translated against the ambient resource, and arequired()validator sets the required marker.
The Vue differences page lists the same changes with the other add-on notes.