FormField
A consistent label, help text and error message around a control you own.
Playground and Examples use the shared browser runtime. For Service Studio inputs and event wiring, use Implementation and API.
Open OutSystems showcase ↗Implement FormField in your application
Use the same setup, screen state and event mapping workflow as the existing components. These components are included in Community 1.1.0.
A consistent label, help text and error message around a control you own. These components are included in @blukavantui/community 1.1.0.
Prepare your local Web project
Use a TypeScript application with a CSS-capable bundler. Install @blukavantui/community@1.1.0 from npm, or install the matching downloaded archive.
// In your test application’s package.json dependencies:
"@blukavantui/community": "1.1.0"Run npm install in that test application to install the component package. Load the theme once and the component stylesheet shown in the adapter. Create an empty host in your view:
<div id="example"></div>Mount one typed instance
This reusable adapter follows the Button and TimePicker guide pattern. Its types come from the factory, so required options stay required. Attach the host first; mount once per view and retain the handle.
import { createFormField } from '@blukavantui/community/form-field'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/form-field.css'
type Options = NonNullable<Parameters<typeof createFormField>[1]>
export function mount(host: HTMLElement, options: Options) {
const instance = createFormField(host, options)
return { instance, update: (patch: Partial<Options>) => instance.update(patch), destroy: () => instance.destroy() }
}
Connect state and updates
FormField owns the label, help/error presentation and ARIA association around your control. Your application owns its value, events, native constraints and listeners. There is no getValue(), validate() or Change callback on FormField.
Set name, required, disabled and readOnly directly on the consumer input. Pass Control at mount, and remove your own listeners during disposal.
Wrap a control you own
Use: Control, Label, Description, Required
Create the input, put its own Name/required/disabled/readOnly constraints on it, and pass it as Control. FormField adds the label, help/error text and ARIA association. It has no value or change callback.
Click the label, inspect the accessible description and confirm focus reaches your actual input.
Manage validation and cleanup explicitly
Use: Error, update, destroy
Set native custom validity on the input yourself; FormField Error only presents the message and ARIA state. On removal, detach your listeners and destroy the field. Its original id and owned ARIA attributes are restored; it does not restore the input’s previous DOM parent.
Clear errors after correction; destroy and confirm your prior aria-describedby value returns.
Use the complete working example
Save this as form-field.ts, import its mount function and pass your attached host. It includes application state, interactive controls and a destroy handler.
Download the editable TypeScript example ↓
Complete FormField example
import { createFormField } from '@blukavantui/community/form-field'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/form-field.css'
export function mount(host: HTMLElement) {
const root = document.createElement('section')
const target = document.createElement('div')
const check = document.createElement('button')
check.type = 'button'; check.textContent = 'Validate reference'
const status = document.createElement('p'); status.setAttribute('role', 'status')
const input = document.createElement('input')
input.type = 'text'; input.name = 'reference'; input.required = true
input.className = 'blu-input'
const existingHelp = document.createElement('p')
existingHelp.id = `reference-help-${crypto.randomUUID()}`
existingHelp.textContent = 'Use your customer reference.'
input.setAttribute('aria-describedby', existingHelp.id)
root.append(target, existingHelp, check, status); host.append(root)
const instance = createFormField(target, {
Control: input, Label: 'Customer reference', Required: true,
Description: 'At least three characters, for example ABC.',
})
const listeners = new AbortController()
input.addEventListener('input', () => {
input.setCustomValidity(''); instance.update({ Error: '' })
status.textContent = `Draft: ${input.value}`
}, { signal: listeners.signal })
check.addEventListener('click', () => {
const error = input.value.trim().length < 3 ? 'Enter at least three characters.' : ''
// FormField supplies presentation/ARIA; the consumer owns constraints.
input.setCustomValidity(error); instance.update({ Error: error })
status.textContent = input.reportValidity() ? 'Reference ready.' : error
}, { signal: listeners.signal })
return { instance, input, destroy() {
listeners.abort(); input.setCustomValidity(''); instance.destroy(); root.remove()
} }
}
Verify behavior and dispose
- Use Tab to reach the control. Space toggles choices; radio groups support arrow navigation. Inputs retain standard text-editing shortcuts.
- Check label focus, help/error associations and your own browser constraints. Restore or remove your consumer control as appropriate.
- Call the example handle’s
destroy()when the view is removed. Abort your application listeners and requests; ignore any asynchronous result after disposal. - Mount again and confirm there is one instance and one callback per interaction.
Reference the native producer
In Manage Dependencies, select FormField from BluKavantUi_Community_Inputs, together with the structures and public actions used by its API. Refresh existing references before publishing your consumer. Inherit the shared Community theme in your application's theme.
Place the Block in your screen
ExampleScreen — Screen (your application)
└─ ExistingLayout — referenced Layout Block instance (child of ExampleScreen)
└─ Content — existing slot on ExistingLayout
└─ ComponentRegion — Container (child of that slot)
└─ MainComponent — BluKavantUi_Community_Inputs.FormField Block instance (child of ComponentRegion)Reuse your screen’s existing Layout and its content slot; do not add a second layout. If your Layout names that slot differently, use its actual content slot. ComponentRegion is a new Container; MainComponent is a referenced Block instance.
Populate these existing slots on MainComponent with your own widgets. Do not create new Placeholder widgets to stand in for a referenced Block's slots.
| Existing slot | Content responsibility |
|---|---|
| Control | Place the actual input/control here. The consumer owns its value and events. |
Create and bind screen state
Create local variables on ExampleScreen for the values your application changes. Use the exact native types below; lists are typed OutSystems Lists, while inputs ending in Json are Text containing the documented JSON. Bind each MainComponent.Input to its matching StateInput local. Constants can instead use the default directly. Fill required data from your Aggregate or Data Action before enabling interaction.
| MainComponent input | Screen local / native type | Initial value | Purpose |
|---|---|---|---|
| Label | StateLabel · Text | "" | Visible accessible label. Supply a meaningful label even when the value has a placeholder. |
| Description | StateDescription · Text | "" | Supporting text associated with the control through aria-describedby. |
| Error | StateError · Text | "" | Application validation message; a non-empty value marks the control invalid. Clear it after validation succeeds. |
| Required | StateRequired · Boolean | False | Marks the field required. This is runtime validation, not a mandatory Block input, and does not register the control with OutSystems Form.Valid. |
| ControlId | StateControlId · Text | "" | Runtime HTML id of the actual focusable control in the Control placeholder, not the wrapper Block id. Used for label/help/error association. |
All native input defaults
| Input | Type | Default | Required |
|---|---|---|---|
| Label | Text | "" | No |
| Description | Text | "" | No |
| Error | Text | "" | No |
| Required | Boolean | False | No |
| ControlId | Text | "" | No |
| ExtendedClass | Text | "" | No |
| EnableMotion | Boolean | False | No |
| MotionPreference | Text | "System" | No |
| UseDefaultMotionDuration | Boolean | True | No |
| MotionDurationMs | Integer | 450 | No |
| MotionEasing | Text | "standard" | No |
Create the event handlers and map every argument
For each event you use, create the named screen Client Action below. Add its listed input parameters with the exact types. Select that action on MainComponent's event property, then enter each mapping explicitly. Event arguments and screen locals are different scopes.
This Block has no events to wire. The consuming screen controls its values and child content.
Associate the field with its control
Place your focusable input in Control. Bind ControlId to that input’s runtime HTML id. FormField manages label, help, required and invalid ARIA attributes; it does not own the input value or emit a change event. Wire the inner control’s event to your screen state. Do not pass a referenced Block wrapper id as ControlId.
Verify in your consumer
Publish the consumer, supply representative data, trigger each connected event and inspect the mapped arguments. Check a failed request, a retry and leaving/reopening the screen. The table describes the native contract; the Web recipes can contain capabilities with different native names or availability. Use these native names and the API tab as the authority for Service Studio.
Shared component motion settings
Use createMotionComponents from the local bluui-community/src/component-motion.ts entry. The factories retain the component’s data, options, events and methods, and add setMotion(settings?). Create the controller with createMotionController from bluui-community/src/motion.ts.
| Setting / method | Behavior |
|---|---|
mode | inherit (default), reduced, or off. The owning controller and operating system can always reduce or disable motion. |
duration | Optional finite-transition override, 0–2000 ms. Omit for default timings; zero is immediate. Reduced caps permitted fades at 200 ms. Continuous loading retains its own cadence. |
easing | Optional standard, ease-in, ease-out, ease-in-out, linear or spring. Omit for each component's default easing. |
ui.configure(settings?) | Replace collection defaults, update existing instances and cancel their unfinished decoration. Explicit instance overrides remain. Omit settings to restore component defaults. |
component.setMotion(settings?) | Replace just this instance's overrides. Omit settings to inherit the collection again. Inputs, focus, content and actions are retained. |
ui.destroy() | Dispose owned instances and their motion before destroying the controller. No global listener is added at import time. |
Validation rejects nonfinite, negative and over-2000-ms durations before changing settings. Motion follows the existing component behavior. Grid rows need stable keys; charts update their actual data series; workflow transitions retain their approval and save contracts. Charts respect EnableAnimation: false; continuous Sparkline glow keeps its own cadence and Pause control. Ripple, glow, shimmer and page-entry helpers remain explicit additions.
Open the playground motion controls · Read the motion and OutSystems integration guide
Current playground settings
Open the playground to load the local source example.FormField with application state
These components are included in @blukavantui/community 1.1.0. This is the same complete example used by the implementation guide, with real controls, event handling and cleanup.
import { createFormField } from '@blukavantui/community/form-field'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/form-field.css'
export function mount(host: HTMLElement) {
const root = document.createElement('section')
const target = document.createElement('div')
const check = document.createElement('button')
check.type = 'button'; check.textContent = 'Validate reference'
const status = document.createElement('p'); status.setAttribute('role', 'status')
const input = document.createElement('input')
input.type = 'text'; input.name = 'reference'; input.required = true
input.className = 'blu-input'
const existingHelp = document.createElement('p')
existingHelp.id = `reference-help-${crypto.randomUUID()}`
existingHelp.textContent = 'Use your customer reference.'
input.setAttribute('aria-describedby', existingHelp.id)
root.append(target, existingHelp, check, status); host.append(root)
const instance = createFormField(target, {
Control: input, Label: 'Customer reference', Required: true,
Description: 'At least three characters, for example ABC.',
})
const listeners = new AbortController()
input.addEventListener('input', () => {
input.setCustomValidity(''); instance.update({ Error: '' })
status.textContent = `Draft: ${input.value}`
}, { signal: listeners.signal })
check.addEventListener('click', () => {
const error = input.value.trim().length < 3 ? 'Enter at least three characters.' : ''
// FormField supplies presentation/ARIA; the consumer owns constraints.
input.setCustomValidity(error); instance.update({ Error: error })
status.textContent = input.reportValidity() ? 'Reference ready.' : error
}, { signal: listeners.signal })
return { instance, input, destroy() {
listeners.abort(); input.setCustomValidity(''); instance.destroy(); root.remove()
} }
}
Mount and dispose
Call mount after the host is attached to the document. Retain the returned handle and call destroy from your view’s unmount hook.
import { mount } from './form-field'
const host = document.getElementById('example')
if (!host) throw new Error('Missing example host')
const view = mount(host)
// Register this with your framework/router’s unmount hook:
function disposeView() { view.destroy() }Use instance.update(patch) for changes while mounted; creating another instance on each render leaves duplicate controls.
Build with native Blocks
Configure the referenced Block in Service Studio using typed inputs, screen state and Client Actions. The Implementation tab includes the widget tree and exact event argument mappings.
Shared component motion settings
Use createMotionComponents from the local bluui-community/src/component-motion.ts entry. The factories retain the component’s data, options, events and methods, and add setMotion(settings?). Create the controller with createMotionController from bluui-community/src/motion.ts.
| Setting / method | Behavior |
|---|---|
mode | inherit (default), reduced, or off. The owning controller and operating system can always reduce or disable motion. |
duration | Optional finite-transition override, 0–2000 ms. Omit for default timings; zero is immediate. Reduced caps permitted fades at 200 ms. Continuous loading retains its own cadence. |
easing | Optional standard, ease-in, ease-out, ease-in-out, linear or spring. Omit for each component's default easing. |
ui.configure(settings?) | Replace collection defaults, update existing instances and cancel their unfinished decoration. Explicit instance overrides remain. Omit settings to restore component defaults. |
component.setMotion(settings?) | Replace just this instance's overrides. Omit settings to inherit the collection again. Inputs, focus, content and actions are retained. |
ui.destroy() | Dispose owned instances and their motion before destroying the controller. No global listener is added at import time. |
Validation rejects nonfinite, negative and over-2000-ms durations before changing settings. Motion follows the existing component behavior. Grid rows need stable keys; charts update their actual data series; workflow transitions retain their approval and save contracts. Charts respect EnableAnimation: false; continuous Sparkline glow keeps its own cadence and Pause control. Ripple, glow, shimmer and page-entry helpers remain explicit additions.
Open the playground motion controls · Read the motion and OutSystems integration guide
Current playground settings
Open the playground to load the local source example.createFormField
FormField owns the label, help/error presentation and ARIA association around your control. Your application owns its value, events, native constraints and listeners. There is no getValue(), validate() or Change callback on FormField.
import { createFormField } from '@blukavantui/community/form-field';
import '@blukavantui/community/form-field.css';Load @blukavantui/community/theme.css once in your app shell.createFormField(host: HTMLElement, initial?: FormFieldOptions)These components are included in @blukavantui/community 1.1.0. Required/optional and signatures come from the 1.1.0 TypeScript source. Defaults below describe runtime behavior, which may differ from the Playground’s selected example.
Options
11| Name | Type / requirement | Usage |
|---|---|---|
Control | HTMLElement | undefinedOptional | Default: Not set. Consumer-owned HTMLElement moved into the field’s content slot. Use a focusable input for label association. Keep value, native constraints, listeners and disposal under your control. |
Id | string | undefinedOptional | Default: Generated once. Control id used for label/help association. Set when mounting; changing Id later does not replace the generated identity. |
Name | string | undefinedOptional | Default: Not applied to the child. Inherited field option. Set this property on your own Control directly; FormField does not set its native name/disabled/readOnly behavior. |
Label | string | undefinedOptional | Default: "". Visible accessible label. Placeholder text does not replace a label. |
Description | string | undefinedOptional | Default: "". Supporting text associated with the control through aria-describedby. |
Error | string | undefinedOptional | Default: "". Displays the error and marks the child aria-invalid. Set Control.setCustomValidity yourself when browser constraint validation must also fail. |
Required | boolean | undefinedOptional | Default: false. Shows the required marker and aria-required. Set the native Control.required property yourself; FormField does not enforce it. |
Disabled | boolean | undefinedOptional | Default: Not applied to the child. Inherited field option. Set this property on your own Control directly; FormField does not set its native name/disabled/readOnly behavior. |
ReadOnly | boolean | undefinedOptional | Default: Not applied to the child. Inherited field option. Set this property on your own Control directly; FormField does not set its native name/disabled/readOnly behavior. |
Size | FieldSize | undefinedOptional | Default: "Medium". Small, Medium or Large. Uses the shared field sizing tokens. |
ExtendedClass | string | undefinedOptional | Default: "". Additional space-separated classes on the root. Keep application CSS scoped. |
Callbacks
No component callbacks. Connect behavior to your application-owned content.
Instance methods & properties
4| Name | Signature / type | Usage |
|---|---|---|
element | HTMLFieldSetElement | HTMLDivElement | Root DOM element owned by the component. Mount into a real host before using focus or validation. |
content | HTMLDivElement | FormField’s DOM slot. update({Control}) moves the supplied node here and replaces the previous control. The consumer still owns that control’s listeners and value. |
update | (patch: Partial<FormFieldOptions>) => void | Shallow-merge an options patch into the current configuration. Arrays/adornment objects replace their previous value. User-change callbacks are not emitted. Call before destroy; updates afterward throw. |
destroy | () => void | Restore the consumer control’s original id and owned ARIA attributes, then remove the wrapper. Does not remove your listeners or restore its original DOM parent. Repeated calls are safe. |
Related data types
Expand a type for its exact fields and allowed values. Pass objects and arrays directly in Web code.
FormFieldOptions
Current local TypeScript declaration. Use the component-specific options and callbacks shown above.
interface FormFieldOptions extends FieldOptions { Control?: HTMLElement; }FieldOptions
Common typed field options. FormField only applies presentation and ARIA; see its option notes for properties managed by the consumer.
interface FieldOptions {
Id?: string; Name?: string; Label?: string; Description?: string; Error?: string;
Required?: boolean; Disabled?: boolean; ReadOnly?: boolean; Size?: FieldSize;
ExtendedClass?: string;
}FieldSize
Allowed shared input sizes.
type FieldSize = 'Small' | 'Medium' | 'Large';Verified native contract for BluKavantUi_Community_Inputs → Essentials → FormField. Bindings, events and layout were verified in the compiled development consumer on 2026-10-24.
Browser checks cover labels, keyboard interactions, values and cleanup. Scoped Safari VoiceOver and browser checks passed; this is not a full-library conformance certification. Test your composed application with its actual content and assistive technologies.
Purpose and use
A consistent label, help text and error message around a control you own.
Keyboard design guidance
Use Tab to reach the control. Space toggles choices; radio groups support arrow navigation. Inputs retain standard text-editing shortcuts.
See the support status above for review coverage and any limitations.
In your application
Provide useful labels, preserve visible focus, and test the component with your actual content. Use text alongside status colors and check contrast in each state.
Events0No events yet.
Interact with an example to see its events.