form_foundation 0.1.0
form_foundation: ^0.1.0 copied to clipboard
Schema-agnostic building blocks for form UIs: themable field widgets, editor groups, labels, and error panels shared by form generators.
form_foundation #
Schema-agnostic building blocks for automatic form generators: typed field widgets, editor groups, list scaffolding, and a shared theme — the presentation layer a generator drives, with no schema logic of its own.
This package is the foundation under
json_schema_form_builder
(forms generated from JSON Schema). It is published separately so that other
generators — a different schema dialect, a server-driven form engine, a
hand-rolled admin panel — can reuse the same widgets and get the same look
without pulling in any JSON Schema machinery.

Design #
- The app (or the generator) owns the data. Every widget is props-in/callbacks-out: it renders a plain Dart value and reports the edited value back. No form state objects, no controllers to manage.
- The app owns the validation. Widgets display the error strings they are given; they never decide what is valid.
- One theme for everything.
FormTheme/FormThemeDataconfigure paddings, group frames, and code text style for every widget below them, the way Material component themes do. - Generator-side contracts are minimal.
FieldHandler,FieldHandlerRegistry, andNodeStatedefine just enough for a generator to map nodes of its model to editors; nothing in them is specific to any schema language.
What's inside #
Typed field widgets — a consistent Material look for the common scalar types:
| Widget | Edits |
|---|---|
StringField |
text, with optional examples suggestions and maxLength |
IntField, DoubleField |
numbers, with canonicalization on blur |
BoolField |
booleans (nullable-aware) |
StringDateField, StringTimeField, StringDateTimeField |
ISO date/time strings via pickers |
StringDataUrlField |
binary content as a data URL, via a pluggable DataUrlFilePicker |
Structure and scaffolding:
EditorGroup— the framed/expanded container with a title, help text, and an error panel; the visual unit of nesting (EditorGroupViewselects the presentation).ListEditor+RemovableRow+AddButton— rows with remove buttons, reordering, and a typed "add" menu, for collection editors.NodeEditor— renders a node through aFieldHandlerRegistry, the bridge a generator plugs its handlers into.CollapsedValueField— the compact "summary + edit button" representation a responsive editor collapses into (WidthThresholdBuilderdecides when).FormDialog,ErrorsPanel,FieldLabel— the dialog wrapper, the error list, and the label with the required marker used by all of the above.
Usage #
The widgets compose like any other Flutter widgets; the app keeps the values and passes errors in:
FormTheme(
data: const FormThemeData(
fieldPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: Column(
children: [
StringField(
title: 'Name',
required: true,
value: name,
error: name.trim().isEmpty ? 'Name is required' : null,
onChanged: (value) => setState(() => name = value),
),
EditorGroup(
title: 'Phones',
content: Column(
children: [
for (final (i, phone) in phones.indexed)
RemovableRow(
field: StringField(
title: 'Phone ${i + 1}',
value: phone,
onChanged: (value) => setState(() => phones[i] = value),
),
onRemove: () => setState(() => phones.removeAt(i)),
),
],
),
),
],
),
)
The example/ app is a complete hand-written contact form built this way —
no generator involved.
When to use what #
- You want forms generated from JSON Schema — use
json_schema_form_builder; it drives these widgets for you. - You are building a form generator for another schema language or a
server-driven UI — depend on this package and implement your handlers
against
FieldHandler/FieldHandlerRegistry. - You are writing a custom field handler for
json_schema_form_builderand want it to match the built-in editors — build its UI from these widgets.
