Skip to main content

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.

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.

Motion

Use MotionJsonTextareaField for a spring-powered entrance animation with blur powered by motion.

API Reference

JsonTextareaField

PropTypeDefaultDescription
controlControl<TFieldValues>-React Hook Form control object from useForm
nameFieldPath<TFieldValues>-Field path in your form values
labelstring-Visible label rendered above the editor
descriptionstring-Helper text rendered under the label
classNamestring-Additional CSS classes for the field root
actionsClassNamestring-Additional CSS classes for the action buttons row
isEditablebooleantrueDisables editing and save actions when set to false
allowArraybooleantrueWhen false, only JSON objects are accepted (arrays rejected)
mode'inline' | 'popover''inline'Presentation mode for the editor
rowsnumber12Number of rows for the inline textarea
placeholderstring'{\n "theme": "dark"\n}'Placeholder content shown when empty
lineNumbersbooleantrueShows or hides the line-number gutter
lineHeightPxnumber20Line-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.
langstring-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"
sheetTitlestring'Edit JSON'Title displayed in the sheet header
textJsonEditorTextSee belowObject 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
}

MotionJsonTextareaField

Same props as JsonTextareaField. Adds spring scaleIn+blur entrance animation via motion. Requires the motion package.

Keyboard shortcuts

  • Ctrl/Cmd + Enter saves the current editor buffer.
  • Esc cancels 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