json editor
A form-aware JSON editor field with inline, popover, and expanded editing modes.
Philosophy
JSON editing is common in advanced settings, integration payloads, and admin tooling. A plain textarea is easy to ship but hard to use safely. JsonTextareaField keeps JSON editing in your form flow, adds validation, formatting, and keyboard shortcuts, and supports compact or expanded editing modes without introducing a heavy code editor dependency.
How It's Built
Loading diagram...
Installation
npx @gentleduck/cli add json-editor
npx @gentleduck/cli add json-editor
Usage
import { JsonTextareaField } from '@/components/ui/json-editor'import { JsonTextareaField } from '@/components/ui/json-editor'import { FormProvider, useForm } from 'react-hook-form'
type FormValues = {
settings: Record<string, unknown> | null
}
const form = useForm<FormValues>({
defaultValues: {
settings: {
theme: 'system',
notifications: true,
},
},
})
<FormProvider {...form}>
<form>
<JsonTextareaField
control={form.control}
name="settings"
label="Settings"
/>
</form>
</FormProvider>import { FormProvider, useForm } from 'react-hook-form'
type FormValues = {
settings: Record<string, unknown> | null
}
const form = useForm<FormValues>({
defaultValues: {
settings: {
theme: 'system',
notifications: true,
},
},
})
<FormProvider {...form}>
<form>
<JsonTextareaField
control={form.control}
name="settings"
label="Settings"
/>
</form>
</FormProvider>Examples
Inline + sheet expansion
Default mode renders the editor inline and opens an expanded sheet editor with the Full action.
Popover mode
Use mode="popover" to keep layout compact and open the editor only when needed.
Custom full-editor callback
Set expandMode="callback" to hand off full-screen editing to your own flow.
API Reference
JsonTextareaField
| Prop | Type | Default | Description |
|---|---|---|---|
control | Control<TFieldValues> | -- | React Hook Form control object from useForm |
name | FieldPath<TFieldValues> | -- | Field path in your form values |
label | string | -- | Visible label rendered above the editor |
description | string | -- | Helper text rendered under the label |
className | string | -- | Additional CSS classes for the field root |
actionsClassName | string | -- | Additional CSS classes for the action buttons row |
isEditable | boolean | true | Disables editing and save actions when set to false |
allowArray | boolean | true | When false, only JSON objects are accepted (arrays rejected) |
mode | 'inline' | 'popover' | 'inline' | Presentation mode for the editor |
rows | number | 12 | Number of rows for the inline textarea |
placeholder | string | '{\n "theme": "dark"\n}' | Placeholder content shown when empty |
lineNumbers | boolean | true | Shows or hides the line-number gutter |
lineHeightPx | number | 20 | Line-height used by textarea and line-number gutter |
dir | 'ltr' | 'rtl' | -- | Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> 'ltr'). Controls editor chrome (gutter/buttons); JSON content stays LTR. |
lang | string | -- | BCP 47 locale tag (e.g. 'ar', 'fa'). Controls the numeral system used for line numbers. |
expandMode | 'none' | 'callback' | 'sheet' | 'sheet' | Full-editor behavior for the Full action |
sheetSide | 'left' | 'right' | 'right' | Side used when expandMode="sheet" |
sheetTitle | string | 'Edit JSON' | Title displayed in the sheet header |
text | JsonEditorText | See below | Object of translatable UI strings for i18n support |
onExpandEditor | (payload: JsonEditorExpandPayload<TFieldValues>) => void | -- | Callback invoked when expandMode="callback" and Full is pressed |
JsonEditorText
All fields are optional. Omitted keys fall back to their English defaults.
type JsonEditorText = {
format?: string // "Format"
cancel?: string // "Cancel"
save?: string // "Save"
full?: string // "Full"
close?: string // "Close"
keepEditing?: string // "Keep editing"
discard?: string // "Discard"
discardTitle?: string // "Discard changes?"
discardDescription?: string // "You have unsaved changes..."
statusHint?: string // "Ctrl/Cmd + Enter: Save, Esc: Cancel"
sheetStatusHint?: string // "Ctrl/Cmd + Enter: Save, Esc: Close"
unsavedChanges?: string // "Unsaved changes"
saved?: string // "Saved"
nullPreview?: string // "NULL"
}type JsonEditorText = {
format?: string // "Format"
cancel?: string // "Cancel"
save?: string // "Save"
full?: string // "Full"
close?: string // "Close"
keepEditing?: string // "Keep editing"
discard?: string // "Discard"
discardTitle?: string // "Discard changes?"
discardDescription?: string // "You have unsaved changes..."
statusHint?: string // "Ctrl/Cmd + Enter: Save, Esc: Cancel"
sheetStatusHint?: string // "Ctrl/Cmd + Enter: Save, Esc: Close"
unsavedChanges?: string // "Unsaved changes"
saved?: string // "Saved"
nullPreview?: string // "NULL"
}JsonEditorExpandPayload
type JsonEditorExpandPayload<TFieldValues extends FieldValues> = {
name: FieldPath<TFieldValues>
rawText: string
value: unknown
}type JsonEditorExpandPayload<TFieldValues extends FieldValues> = {
name: FieldPath<TFieldValues>
rawText: string
value: unknown
}Keyboard shortcuts
Ctrl/Cmd + Entersaves the current editor buffer.Esccancels inline edits or closes the expanded sheet.
RTL Support
Set dir="rtl" on JsonEditor for a local override, or set DirectionProvider once at app/root level for global direction. Pass lang for locale-aware line numbers, and use text to translate labels/messages.
See also
- React Hook Form — Form context components used by
JsonTextareaField - Textarea — Basic plain-text multiline input
- Field — Layout primitive for label + description + error composition