Build Form: $ARGUMENTS
If $ARGUMENTS is empty, ask the user: (1) what the form is for, (2) which fields it needs, (3) whether validation is required.
Phase 1: Determine Scope
Parse from $ARGUMENTS or ask:
- Form name (PascalCase, e.g.,
UserProfile,ShippingAddress) - Fields: name, type (
text|number|select|date|checkbox|textarea), required (default) or optional (suffix with?, e.g.,birthDate:date?) - Layout:
1column |2columns (default) |3columns - Submit target: inline handler, emitted event to parent, or none
Phase 2: Gather Component Context
Call the @fundamental-ngx/mcp MCP server:
get_usage_guide('form')— composition order,fdp-form-groupvsfd-form-itemdecision tree, pitfallsget_component_api('fdp-form-group')— layout inputs,(onSubmit)outputget_component_api('fdp-form-field')—[id],[label],[required],[columns], hint/error slots- For each non-text field:
get_component_apifor its control (e.g.,fdp-select,fdp-date-picker,fdp-checkbox)
If MCP is unavailable, read libs/mcp-server/src/data/usage-guides.ts.
Phase 3: Present Plan
Output this table before writing any code:
## Form Plan: [FormName]
**Layout:** 2 columns | **Validation trigger:** on submit + on touched
| Field | Control | Required | Validators |
|-------|---------|----------|------------|
| firstName | fdp-input | yes | minLength(2) |
| role | fdp-select | yes | required |
| birthDate | fdp-date-picker | no | — |
Stop here and wait for approval before generating code.
Phase 4: Generate Component
Create three files in the target path (ask the user if not already known).
Also generate the fields interface at the top of the .component.ts file:
export interface [Name]Fields {
// one property per form field, e.g.:
firstName: string;
role: string;
}
TypeScript (.component.ts)
Import each platform form component directly — they are all standalone. There is no barrel PlatformFormModule to import.
import { ChangeDetectionStrategy, Component, inject, output } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonModule } from '@fundamental-ngx/core/button';
import {
FormFieldComponent,
FormFieldErrorDirective,
FormGroupComponent,
InputComponent
// Add per-field: SelectComponent, TextAreaComponent, etc.
} from '@fundamental-ngx/platform/form';
// Import OptionItem only when using fdp-select:
// import { OptionItem } from '@fundamental-ngx/platform/shared';
@Component({
selector: 'app-[kebab-name]-form',
templateUrl: './[kebab-name]-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ReactiveFormsModule,
FormGroupComponent,
FormFieldComponent,
FormFieldErrorDirective, // always required when any field has validators
InputComponent,
// SelectComponent, TextAreaComponent, etc.
ButtonModule
]
})
export class [Name]FormComponent {
readonly submitted = output<[Name]Fields>();
private readonly _fb = inject(FormBuilder);
form: FormGroup = this._fb.group({
// fieldName: [defaultValue, [Validators.required, ...]]
});
onSubmit(): void {
if (this.form.valid) {
this.submitted.emit(this.form.getRawValue() as [Name]Fields);
}
}
}
Standalone component imports reference:
| Template element | Import from @fundamental-ngx/platform/form |
|---|---|
fdp-form-group |
FormGroupComponent |
fdp-form-field |
FormFieldComponent |
fdp-input |
InputComponent |
fdp-select |
SelectComponent |
fdp-textarea |
TextAreaComponent |
fdp-date-picker |
PlatformDatePickerComponent |
fdp-checkbox |
CheckboxComponent |
fdpFormFieldError (template directive) |
FormFieldErrorDirective |
HTML Template (.component.html)
Wrap everything in a native <form> — FormGroupComponent does NOT render a <form> tag by default (useForm defaults to false) and has no footer slot. Submit/reset buttons must be placed outside <fdp-form-group> but inside the <form>.
Use columnLayout="XL2-L2-M2-S1" for a 2-column layout (not [layout]).
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<fdp-form-group [formGroup]="form" columnLayout="XL2-L2-M2-S1">
<!--
Error message templates — shared across ALL fields in this group.
Required whenever any fdp-form-field has [required]="true" or its
FormControl carries validators. Omitting these causes a runtime error:
"Validation strings are required for the any provided validations."
Add one <ng-template> per Angular error key your validators produce.
-->
<ng-template fdpFormFieldError="required">This field is required</ng-template>
<ng-template fdpFormFieldError="minlength" let-error
>Minimum {{ error.requiredLength }} characters required</ng-template
>
<ng-template fdpFormFieldError="email">Please enter a valid email address</ng-template>
<!-- add more for maxlength, pattern, custom validators, etc. -->
<fdp-form-field id="firstName" label="First Name" [required]="true" [column]="1">
<fdp-input name="firstName" [formControlName]="'firstName'"></fdp-input>
</fdp-form-field>
<!-- repeat fdp-form-field blocks for each field -->
</fdp-form-group>
<!-- Buttons go here, outside fdp-form-group but inside <form> -->
<div style="display: flex; gap: 0.5rem; margin-top: 1rem">
<button fd-button fdType="emphasized" type="submit">Submit</button>
<button fd-button fdType="transparent" type="button" (click)="form.reset()">Reset</button>
</div>
</form>
fdp-select usage — use [list] with OptionItem[] from @fundamental-ngx/platform/shared. Each item's .value is what gets written to the FormControl:
<fdp-form-field id="role" label="Role" [required]="true" [column]="2">
<fdp-select name="role" [formControlName]="'role'" [list]="roleOptions"> </fdp-select>
</fdp-form-field>
import { OptionItem } from '@fundamental-ngx/platform/shared';
readonly roleOptions: OptionItem[] = [
{ label: 'Developer', value: 'developer' },
{ label: 'Designer', value: 'designer' },
];
SCSS (.component.scss)
Leave empty — form spacing and layout are handled by fundamental-styles. Only add custom rules if product requirements demand it.
Critical Rules
fdpFormFieldErrortemplates are mandatory when any field has validators — if[required]="true"is set on anyfdp-form-field, or itsFormControlcarries validators, the component throws at runtime:"Validation strings are required for the any provided validations."Fix: add<ng-template fdpFormFieldError="required">...</ng-template>(and one per additional error key) as direct children offdp-form-group. They are shared automatically by all fields. ImportFormFieldErrorDirectivefrom@fundamental-ngx/platform/form.- One
<ng-template fdpFormFieldError>per Angular error key — add templates for every key your validators can produce:required,minlength,maxlength,email,pattern, and any custom keys. The template context (let-error) exposes the error value (e.g.error.requiredLengthforminlength). - Import standalone components directly — there is no
PlatformFormModule. Import each component individually:FormGroupComponent,FormFieldComponent,FormFieldErrorDirective,InputComponent,SelectComponent,TextAreaComponent, etc. fdp-form-fieldnotfdp-form-item—fdp-form-itemis the corefd-API; platform forms usefdp-form-field- Wrap in
<form (ngSubmit)>, NOT(fdSubmit)onfdp-form-group—fdp-form-groupdoes not emitfdSubmit. Use a standard<form [formGroup]="form" (ngSubmit)="onSubmit()">wrapper. The component's own(onSubmit)output fires only when[useForm]="true"is set. columnLayout(string), NOT[layout](object) —FormGroupComponentacceptscolumnLayout="XL2-L2-M2-S1", not[layout]="{ columns: 2 }". The format isXLn-Ln-Mn-Sn.- No
[fdpFormFooter]directive — it does not exist. Place buttons outside<fdp-form-group>but inside your<form>wrapper. [list](not[options]) onfdp-select— passOptionItem[]to[list]. Do NOT use[options],displayKey, orvalueKey— these inputs do not exist onSelectComponent.[id]onfdp-form-fieldis required — it sets theforon the label and theidon the inner control via content projection; omitting it breaks label–control association and accessibilitynameattribute on every control — must be unique within the form; matches theFormControlName[column](singular) not[columns]—fdp-form-fielduses[column]to set which column it occupies in the grid;[columns]is the span count- Do NOT add
standalone: true— default since Angular 19 - Do NOT use
ngClassorngStyle— use direct class/style bindings
Phase 5: Validate
nx run <project>:build # or: ng build
Fix any TypeScript type errors or missing import errors before reporting done.
Output
## Build Form: [FormName]
**Files generated:**
- src/app/.../[kebab-name]-form.component.ts
- src/app/.../[kebab-name]-form.component.html
- src/app/.../[kebab-name]-form.component.scss
**Imports required in parent:**
- `import { [Name]FormComponent } from './[kebab-name]-form/[kebab-name]-form.component'`
**Next steps:**
- [ ] Add [Name]FormComponent to the parent's imports array
- [ ] Handle the (submitted) output event in the parent
- [ ] Customize per-field error messages with fdp-form-message if default messages are insufficient
- [ ] For multi-step forms, wrap multiple fdp-form-group instances in a wizard container