Build Form: $ARGUMENTS
SkillDev toolsBuild a reactive form with @fundamental-ngx/platform form components, FormGroup wiring, validation, and error states
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Build Form: $ARGUMENTS skill
What this skill tells your AI
The instructions your AI receives, as published by sap/fundamental-ngx in .claude/skills/build-form/SKILL.md and read by ahel’s review.
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
Signals
- GitHub stars
- 294
- Forks
- 147
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
build-form- Source
- github.com/sap/fundamental-ngx