Field types
Every field is a subtype of the sealed FormFieldDefinition. A switch over a field is exhaustive, and each subtype's copyWith returns its own concrete type.
Shared options
Every field type accepts these parameters.
| Parameter | Type | Meaning |
|---|---|---|
key | String | Required. Stable value key, unique in the form. Values, rules and formulas ($key) use it. |
label | String | Required, non-blank. |
description | String? | A sentence under the label saying what the field is for. |
hint | String? | Example text inside an empty input. |
helpText | String? | Longer guidance behind the field's info button. |
required | bool | Adds a RequiredValidation at index 0 of validations. The required getter reads it back. |
readOnly | bool | The field can't be edited, whatever rules enable. A value a rule sets also shows read-only. It shows as CdxAccess.readOnly: fully legible, never dimmed (see Access). |
validations | Iterable<FormValidation> | Ordered list of rules. See Validation. |
On copyWith, null keeps a text value and a blank string clears description, hint or helpText. copyWith(required: ...) adds or removes only the required rule.
final withHelp = field.copyWith(helpText: 'One line; details go in What happened.');
final optional = field.copyWith(required: false);Built-in types
| Class | JSON type | Own options | Value |
|---|---|---|---|
TextFormField | text | inputKind: TextInputKind (singleLine, multiline, richText, masked) | String |
NumberFormField | number | decimal: bool. Bounds come from NumericRangeValidation. | int, or Rational when decimal |
MoneyFormField | money | defaultCurrency: String? | MoneyValue(amount, currency) |
QuantityFormField | quantity | dimension: String?, units: Iterable<String> (the first is the default; empty means the host's unit lookup offers them) | QuantityValue(amount, unit) |
DurationFormField | duration | none | Duration |
ToggleFormField | boolean | display: ToggleDisplay (toggle, a switch; or checkbox) | bool |
ChoiceFormField | choice | choices: Iterable<FormChoice> (required), multiple: bool, display: ChoiceDisplay (dropdown; radio, every option as a radio button or, for several, a checkbox; or chips) | String, or Set<String> when multiple |
ReferenceFormField | reference | target: String (a host entity type id, required), multiple: bool, scan: bool (a camera button beside the lookup; see Barcode scanning) | record id(s) |
RatingFormField | rating | maxStars: int (default 5, at most 10) | int from 1 to maxStars; null for no rating |
DateTimeFormField | dateTime | kind: TemporalKind (date, time, dateTime); shorthand dateOnly: true | DateTime |
DateRangeFormField | dateRange | none | DateRangeValue(start, end) |
DateTimeRangeFormField | dateTimeRange | none | DateTimeRangeValue(start, end) |
AttachmentFormField | attachment | imagesOnly: bool, multiple: bool | AttachmentValue, or List<AttachmentValue> when multiple |
SignatureFormField | signature | meaning: FormSignatureMeaning (author, reviewer, approver, witness; default author), remarks: FormSignatureRemarks (hidden, optional, required; default hidden), signLabel: String? (the button text, Sign by default), statement: String? (what the signer declares) | SignatureValue; null while unsigned |
FormulaFormField | formula | expression: String, outputType: ValueType (both required). Always read-only; has no required or readOnly parameter. | computed |
CustomFormField<T> | your typeId | typeId, configuration: T, configurationVersion (default 1) | yours |
Minimum sizes for each type are listed on the Layout page.
Choices
ChoiceFormField(
key: 'severity',
label: 'Severity',
required: true,
choices: const [
FormChoice(value: 'minor', label: 'Minor'),
FormChoice(value: 'major', label: 'Major'),
FormChoice(value: 'critical', label: 'Critical'),
],
)Choice values must be non-blank and unique; labels must be non-blank.
Attachments
An attachment's value is an AttachmentValue: the host's reference to the stored file, its name, mediaType and bytes, and sha256, the lowercase hex SHA-256 of the file's bytes (null for a reference the host stored itself and whose bytes the form never saw). The form carries references, never bytes. toJson and AttachmentValue.fromJson write and read it; a checksum that isn't 64 lowercase hex digits is refused.
An image the host can show is a thumbnail that opens a large preview; every other file is a chip that opens the host's viewer, if it has one. Size, count and media-type limits are an AttachmentValidation on the field. See Host services.
Signatures
A signature field holds an electronic signature the host recorded after checking the signer's credentials. Unsigned it shows a Sign button; signed, a receipt that says who signed as what, and when. The value is a SignatureValue and carries no secret:
| Member | Meaning |
|---|---|
signerId, signerName | Who signed, as the host knows them. |
meaning | What the signature attests (FormSignatureMeaning). A field accepts only its own. |
signedAt | When, always in UTC. |
method | How the signer proved who they are: FormSignatureMethod.password or pin. |
receiptId | The host's id of its own record of the signature (an assertion or signature id). |
reason | The signer's reason or remarks, when asked for and given. |
contentDigest | The SHA-256, as lowercase hex, the host computed over what was signed. |
The field never sees a password or a PIN; FormSignatureService is the host's side of the ceremony. A required signature field must be signed to validate; SignatureValidation ("signed") asks the same of an optional one, or when a rule applies it. A value signed as another meaning, or without the reason a field asks for, fails validation. Rules read a signature only as empty or not empty. See Signatures for the service and what it must do.
Host services
Some types need the host. FormHostServices holds a hook for each, which the host gives once to a KitScope<FormKit>; Host services describes them all, and vyuh_form_kit_capture implements the camera and file ones.
| Field | FormHostServices member | Signature |
|---|---|---|
ReferenceFormField | referenceSearch | Future<List<FormChoice>> Function(String target, String query, int pageSize) |
ReferenceFormField | referenceLabel | Future<String?> Function(String target, String value) |
ReferenceFormField (scan) | referenceScanner, referenceScanResolver | Future<String?> Function(BuildContext, ReferenceFormField), and Future<FormChoice?> Function(String target, String scanned) |
MoneyFormField, QuantityFormField | codeSearch | Future<List<FormChoice>> Function(String catalog, String? dimension, String query, int pageSize) (catalog is currency or unit) |
AttachmentFormField | attachmentPicker, attachmentImage, attachmentPreview | Future<List<AttachmentValue>?> Function(BuildContext, AttachmentFormField field, List<AttachmentValue> current) (null when the user cancels); ImageProvider? Function(AttachmentValue); Future<void> Function(BuildContext, AttachmentValue) |
SignatureFormField | signatureService, signatureDigest | a FormSignatureService, and String? Function(FormKitForm) |
CustomFormField, CustomContent | the kit's view parts, not a service | type.withParts(view: ViewPart<CustomFieldInput<C>>((context, input) => …)), and ViewPart<CustomContent<C>> for content; see Custom fields |
Formula fields
FormulaFormField(
key: 'line_cost',
label: 'Line cost',
expression: r'$qty * $unit_cost',
outputType: ValueType.rational, // re-exported by vyuh_form_types
)A formula declares no dependency list: its inputs come from compiling the expression against the fields in its scope. See Rules and formulas.
Content nodes
Content is not a field and has no value. It sits in the layout tree wherever a field slot can.
| Class | JSON type | Options | Rendered by |
|---|---|---|---|
CalloutContent | callout | title (optional plain text), body (inline rich text), variant: CalloutVariant (neutral, info, success, warning, critical; default info), showIcon, showBorder (both default true) | FormView, as a CDX Banner |
ImageContent | image | image (the asset reference, null until chosen), altText, width: ImageWidth (fill or fixed(px)), height: ImageHeight (auto or fixed(px)), fit: ImageFit (contain, cover), align: ImageAlign (start, center, end) | KitScope<FormKit>(services: FormHostServices(assets:)) |
DividerContent | divider | none | FormView |
SpacerContent | spacer | gridUnits (at least 1) | FormView |
CustomContent<T> | custom | typeId, configuration: T, configurationVersion | No renderer yet: FormView shows an "Unsupported content" placeholder |
The codec leaves out callout and image settings at their defaults, so a plain callout is {"type": "callout", "body": "…"}. A fixed width or height is written as its pixels; fill and auto are left out.
Callout
A callout's body is inline rich text: **bold**, *italic*, `code`, ==highlight==, ~~strike~~, ++underline++, ^super^, ~sub~ and [text](url). It renders through the CDX RemarksText, and the callout itself as a CDX Banner of its variant, so the designer's canvas and the filled form look the same.
Image
An image refers to its picture by the reference the host's FormAssetProvider returned:
abstract interface class FormAssetProvider {
Future<String?> pick(BuildContext context, {required Set<String> mediaTypes});
ImageProvider image(String ref);
}The designer calls pick when the author chooses a picture; FormView and the canvas call image to load it. Give the provider once, as FormHostServices(assets:) in the KitScope<FormKit>; FormView and FormKitEditor both take it from there. package:vyuh_form_kit/testing.dart has MemoryAssetProvider, which keeps what an injected picker returns in memory under mem:<n> references.
Outside a grid an image is its width (the container's, or fixed pixels no wider than the container) and its height (the picture's aspect ratio, or fixed pixels). In a grid its placement is its box, and its own width and height don't apply (fillsGridBox). Fit and align place the picture in its box. With no picture chosen, or no provider, the form shows a placeholder saying so; if the picture fails to load, its alt text.
Coming later
Text formats (email, phone, URL) with implicit validation, input masks and sliders are planned. See the Roadmap.