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
Installation
npx @gentleduck/cli add json-editor
npx @gentleduck/cli add json-editor
Usage
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>
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.
RTL Support
Set the direction once with DirectionProvider (or <html dir="rtl">) and the editor follows: labels, buttons, and the expand sheet, popover, and dialog mirror, while the JSON code and its line-number gutter stay LTR. Pass lang for locale-aware line numbers and text to translate labels and messages.
Motion
Use MotionJsonTextareaField for a scaleIn entrance animation (fade, scale, and blur) with a bouncy spring transition, powered by motion.
motion package. Use MotionJsonTextareaField instead of JsonTextareaField. Same props, animated entrance.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' | - | Direction override. Defaults to the nearest DirectionProvider, then <html dir>. Mirrors the editor chrome; the JSON code and gutter stay 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". Mirrors in RTL. |
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"
}
JsonEditorExpandPayload
type JsonEditorExpandPayload<TFieldValues extends FieldValues> = {
name: FieldPath<TFieldValues>
rawText: string
value: unknown
}
MotionJsonTextareaField
Same props as JsonTextareaField. Adds a scaleIn entrance animation (fade, scale, and blur) with a bouncy spring transition via motion. Requires the motion package.
Keyboard shortcuts
Ctrl/Cmd + Entersaves the current editor buffer.Esccancels inline edits or closes the expanded sheet.
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