Dynamic form builder and renderer for Angular with headless UI pattern
npm install @moe1399/ngx-dynamic-formsDynamic form builder and renderer for Angular with a headless UI pattern.
- Headless UI Pattern: Zero default styling, complete styling freedom via data-* attributes
- No Built-in Buttons: You render your own submit/save buttons for full control
- 16 Field Types: text, email, number, textarea, date, daterange, select, radio, checkbox, table, datagrid, phone, info, formref, fileupload, autocomplete
- Visual Form Builder: Drag-drop field ordering, validation rules, sections
- JSON-driven: Define forms via JSON configuration
- Auto-save: Built-in local storage persistence
- URL Sharing: Compressed schema sharing via URL parameters
``bash`
npm install @moe1399/ngx-dynamic-forms
Peer Dependencies:
- Angular 21+
- pako (optional, for URL sharing)
The DynamicForm component does not render submit/save buttons. You provide your own buttons via content projection and call the component methods:
`typescript
import { Component, viewChild } from '@angular/core';
import { DynamicForm, FormConfig } from '@moe1399/ngx-dynamic-forms';
@Component({
selector: 'app-my-form',
imports: [DynamicForm],
template:
[config]="formConfig"
(formSubmit)="onSubmit($event)"
(valueChanges)="onValueChange($event)">
})
export class MyFormComponent {
form = viewChild.required('form'); formConfig: FormConfig = {
id: 'contact-form',
fields: [
{ name: 'name', label: 'Name', type: 'text', validations: [{ type: 'required', message: 'Name is required' }] },
{ name: 'email', label: 'Email', type: 'email' }
],
submitLabel: 'Send'
};
onSubmit(data: any) {
console.log('Form submitted:', data);
}
onValueChange(data: any) {
console.log('Form values changed:', data);
}
// Access form state programmatically
checkFormState() {
const formRef = this.form();
console.log('Current value:', formRef.value);
console.log('Is valid:', formRef.valid);
console.log('Is touched:', formRef.touched);
console.log('Is dirty:', formRef.dirty);
}
}
`$3
`typescript
import { Component } from '@angular/core';
import { NgxFormBuilder, FormConfig } from '@moe1399/ngx-dynamic-forms';@Component({
selector: 'app-builder',
imports: [NgxFormBuilder],
template:
})
export class BuilderComponent {
formConfig: FormConfig | null = null;
}
`$3
`typescript
import { Component, inject } from '@angular/core';
import { FormBuilderService, FormStorage, UrlSchemaService } from '@moe1399/ngx-dynamic-forms';@Component({
// ...
})
export class MyComponent {
private formBuilder = inject(FormBuilderService);
private formStorage = inject(FormStorage);
private urlSchema = inject(UrlSchemaService);
// FormBuilderService - manage form configurations
saveConfig() {
this.formBuilder.saveConfig(this.config);
}
loadConfig(id: string) {
return this.formBuilder.loadConfig(id);
}
// FormStorage - persist form data
saveFormData() {
this.formStorage.saveForm('my-form', this.data);
}
// UrlSchemaService - share forms via URL
async shareForm() {
await this.urlSchema.copyShareUrlToClipboard(this.config);
}
}
`$3
Both components are headless with optional default themes. Import themes in your
styles.scss:`scss
// Import both themes (dynamic form + form builder)
@use '@moe1399/ngx-dynamic-forms/src/styles/ngx-dynamic-forms';// Or import individually
@use '@moe1399/ngx-dynamic-forms/src/styles/themes/default'; // Dynamic Form (.ngx-df)
@use '@moe1399/ngx-dynamic-forms/src/styles/themes/form-builder-default'; // Form Builder (.ngx-fb)
`#### Dynamic Form Theme Customization
The default theme uses CSS custom properties (design tokens) for easy customization. Override these variables on
.ngx-df to customize colors, typography, spacing, and more:`scss
.ngx-df {
// Primary colors
--df-color-primary: #0066cc;
--df-color-primary-dark: #004d99;
--df-color-primary-light: #3399ff; // Backgrounds
--df-color-background: #f0f4f8;
--df-color-surface: #ffffff;
// Typography
--df-font-family: 'Roboto', sans-serif;
--df-font-size-base: 16px;
// Spacing
--df-spacing-md: 12px;
--df-spacing-lg: 16px;
// Borders
--df-border-radius: 4px;
}
`Available CSS Variables:
| Category | Variables |
|----------|-----------|
| Primary Colors |
--df-color-primary, --df-color-primary-dark, --df-color-primary-light |
| Backgrounds | --df-color-background, --df-color-background-light, --df-color-surface, --df-color-surface-alt |
| Borders | --df-color-border, --df-color-border-dark, --df-color-border-light |
| Text | --df-color-text, --df-color-text-muted, --df-color-text-disabled, --df-color-text-inverse |
| State | --df-color-error, --df-color-error-bg, --df-color-warning, --df-color-info |
| Typography | --df-font-family, --df-font-size-base, --df-font-size-sm, --df-font-size-xs, --df-font-size-lg |
| Spacing | --df-spacing-xs (4px), --df-spacing-sm (6px), --df-spacing-md (8px), --df-spacing-lg (10px), --df-spacing-xl (12px), --df-spacing-2xl (16px) |
| Borders | --df-border-radius, --df-border-radius-sm, --df-border-radius-md |
| Shadows | --df-shadow-focus, --df-shadow-popover, --df-shadow-tooltip |
| Components | --df-label-width, --df-input-min-height, --df-button-padding |#### Custom Styles with Data Attributes
Or create custom styles using
data-* attribute selectors:`scss
[data-form-id] {
// Form container styles
}[data-field-valid="false"] {
// Invalid field styles
}
[data-field-touched="true"][data-field-valid="false"] {
// Show errors only after interaction
}
[data-validation-error] {
// Error message styles
}
[data-field-type="text"] {
// Style specific field types
}
`#### Form Builder Theme Customization
The form builder uses
.ngx-fb wrapper class with its own CSS variables:`scss
.ngx-fb {
// Primary colors
--fb-color-primary: #0066cc;
--fb-color-primary-dark: #004d99; // Backgrounds
--fb-color-background: #f0f4f8;
--fb-color-surface: #ffffff;
// Typography
--fb-font-family: 'Roboto', sans-serif;
--fb-font-size-base: 14px;
// Spacing
--fb-spacing-md: 8px;
--fb-spacing-lg: 12px;
// Layout
--fb-left-panel-width: 350px;
}
`Available Form Builder CSS Variables:
| Category | Variables |
|----------|-----------|
| Primary Colors |
--fb-color-primary, --fb-color-primary-dark, --fb-color-primary-light |
| Backgrounds | --fb-color-background, --fb-color-background-light, --fb-color-surface, --fb-color-surface-alt |
| Borders | --fb-color-border, --fb-color-border-dark, --fb-color-border-light |
| Text | --fb-color-text, --fb-color-text-muted, --fb-color-text-inverse |
| State | --fb-color-error, --fb-color-error-bg, --fb-color-success-bg |
| Typography | --fb-font-family, --fb-font-size-base, --fb-font-size-sm, --fb-font-size-xs, --fb-font-size-lg |
| Spacing | --fb-spacing-xs through --fb-spacing-2xl |
| Layout | --fb-left-panel-width, --fb-min-height, --fb-max-panel-height |API
$3
Selector:
ngx-dynamic-formContent Projection: Place your submit/save buttons inside the component tags.
| Input | Type | Description |
|-------|------|-------------|
|
config | FormConfig | Form configuration |
| fileUploadHandler | FileUploadHandler | Handler for uploading files to server |
| fileDownloadHandler | FileDownloadHandler | Handler for downloading files from server |
| fileRemoveHandler | FileRemoveHandler | Handler for deleting files from server (fire-and-forget) |
| readOnly | boolean | Make entire form read-only |
| disabled | boolean | Disable entire form || Output | Type | Description |
|--------|------|-------------|
|
formSubmit | EventEmitter