This document teaches an LLM (or any agent) how to use this package to generate rich, editable forms from JSON with fine-grained renderer control.
- Import
Fieldsfrom the package and render it with:initialJson: the JSON (string) to editfieldConfig: per-field type hints and options- Optional: sections, styling, and custom renderers for full control
import { Fields } from '@sciphergfx/json-fields'
<Fields
initialJson={JSON.stringify({ name: 'Jane', email: 'jane@example.com' })}
fieldConfig={{ email: { type: 'email' } }}
saveButtonText="Save"
cancelButtonText="Reset"
/>Copy–pasteable minimal usage with sections and common field types:
import React from 'react'
import { Fields } from '@sciphergfx/json-fields'
export default function SimpleJsonForm() {
const initialData = { /* ...your object... */ }
const fieldConfig = { /* ...your config... */ }
const sections = [ /* ...your sections... */ ]
return (
<Fields
initialJson={JSON.stringify(initialData)}
fieldConfig={fieldConfig}
sections={sections}
includeUnsectioned
onSave={(nested, flat) => console.log('save', nested, flat)}
onCancel={() => console.log('cancel')}
onFieldChange={(k, v) => console.log('change', k, v)}
saveButtonText="Save Changes"
cancelButtonText="Reset Form"
/>
)
}initialJsonstring: the data to load. The component manages a flat state internally and emits nested data on save.fieldConfigobject: per-field overrides:{ [key]: { type, ...options } }.sections: structure fields into groups:[{ id?, title, description?, fields: string[], collapsible?, defaultOpen? }].inlineLabels: show label and control on one line.columns: number of columns used for layout.customStyles: style hooks for headless primitives (labels, inputs, etc.).renderers: override headless primitives:{ Container, Box, Button, Input, Select, Textarea, Text, Heading, VStack, HStack, Card, Alert, Label }.onSave(nested, flat): called when user saves.onCancel(): called when user resets.onFieldChange(key, value, nestedData): live updates for each field.
If not specified, types are inferred from the value. Set type to control the UI:
text(default)email,url,date,passwordnumber,slider(usemin,max,step)select(useoptions: string[])multi-select(useoptions: string[])textarea(userows)tags(array of strings with chip editor)key-value-list(table-like editor;columns?: string[],showHeader?: boolean)array(generic array; strings become tags editor)object(nested object editor)segment(segmented single-select;options: string[])
Additional metadata:
description?: stringdescriptionPlacement?: 'label' | 'input'(where to render description)
You can override rendering at three levels, in this order of precedence:
customFieldRenderers[key]— full control for a specific fieldcustomInputRenderers[type]— full node override for a typecustomControlRenderers[key|type]— override only the control (label and wrappers remain standard)
Each renderer receives a RendererContext:
{
key: string
value: any
displayName: string
fieldTypeConfig: Record<string, any>
formData: Record<string, any>
onChange: (value: any) => void
UI: Record<string, React.ElementType> // headless primitives
props: FieldsProps // original props passed to Fields
}const customControlRenderers = {
segment: ({ UI, value, onChange, fieldTypeConfig }) => (
<UI.HStack style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{(fieldTypeConfig?.options || []).map((opt) => (
<button
key={opt}
type="button"
onClick={() => onChange(opt)}
style={{ padding: '6px 10px', borderRadius: 8 }}
>
{opt}
</button>
))}
</UI.HStack>
),
}
<Fields initialJson={json} fieldConfig={{ status: { type: 'segment', options: ['draft','review','published'] } }} customControlRenderers={customControlRenderers} />const customFieldRenderers = {
avatarUrl: ({ UI, key, value, onChange }) => (
<UI.Box>
{/* Your custom uploader; call onChange(urlOrFileList) */}
</UI.Box>
),
}
<Fields initialJson={json} customFieldRenderers={customFieldRenderers} />- By default the component renders semantic HTML elements. Provide
renderersto map to your design system. - Use
customStylesfor spacing/visual tweaks: keys includefieldContainer,fieldLabel,input,textarea,select,checkbox,label,text, and control button styles.
Group fields and control visibility/density:
const sections = [
{
id: 'profile',
title: 'Profile',
description: 'Basic information',
fields: ['name','email','status'],
collapsible: true,
defaultOpen: true,
},
]
<Fields initialJson={json} fieldConfig={cfg} sections={sections} includeUnsectioned />- Provide
initialJsonas a string; the component parses and flattens it. - On each change, use
onFieldChangeto capture deltas or side-effects (validation, remote suggestions, etc.). - On save, consume
nestedDataas the authoritative payload.
- Be explicit with
fieldConfigto ensure correct widgets and options. - Use type-level overrides (
customControlRenderers/customInputRenderers) for consistent UX across similar fields. - Use field-level overrides for highly bespoke fields (e.g., map picker, media uploader).
- Explain choices via
description; place atlabelto give context above the control. - Sections keep long forms navigable; mark some as
collapsible.
- Generate
fieldConfigfrom a schema or example object. - Provide
sections(optional) andinitialJson(stringified data). - Attach
onSaveto emit final nested JSON and persist it. - Add render overrides only where needed.
- Source:
lib/components/JsonToFields.jsx - Demo wiring:
src/components/PreviewPanel.jsx - Demo config:
src/constants/demoData.js