upp-form
A structured form layout system composed of multiple sub-components. The main upp-form component wraps an Angular reactive FormGroup and provides a consistent visual structure for sections, action rows, warnings, button bars, and submit/cancel buttons. Each sub-component handles a specific part of the form layout.
When to Use
- You need a consistent, structured form layout across the application.
- Your form has multiple sections with titles and optional icons/images.
- You need inline action rows, warning banners, or floating button bars within a form.
- You want a standardized way to present save/cancel buttons with configurable positioning.
Demo
Source Code
- HTML
- TypeScript
- SCSS
<h2>upp-form</h2>
<p class="demo-description">New product creation form — demonstrates <code>upp-form</code> sections, actions, warnings, button bar, and buttons.</p>
<!-- Controls -->
<div class="demo-controls">
<ion-button size="small" (click)="toggleSectionFill()">Section Fill: {{ sectionFill }}</ion-button>
<ion-button size="small" (click)="toggleWarning()">Warning: {{ showWarning }}</ion-button>
<ion-button size="small" (click)="toggleButtonFloat()">Button Float: {{ buttonFloat }}</ion-button>
</div>
<div class="demo-section">
<h3>New Product</h3>
<upp-form [formGroup]="form">
<!-- Basic info -->
<upp-form-section title="Basic Info" icon="pricetag-outline" [fill]="sectionFill">
<div class="demo-field">
<label class="demo-label">Product Name</label>
<upp-input
formControlName="name"
placeholder="e.g. Café con leche"
title="Name"
[readonly]="false"
[kiosk]="false">
</upp-input>
</div>
<div class="demo-field">
<label class="demo-label">Price</label>
<upp-input
formControlName="price"
placeholder="0.00"
title="Price"
type="price"
[readonly]="false"
[kiosk]="false">
</upp-input>
</div>
</upp-form-section>
<!-- Separator -->
<upp-form-action title="Details" icon="information-circle-outline"></upp-form-action>
<!-- Description -->
<upp-form-section title="Details" [fill]="sectionFill">
<div class="demo-field">
<label class="demo-label">Description</label>
<upp-textarea
formControlName="description"
placeholder="Describe the product..."
title="Description"
[rows]="3"
[readonly]="false"
[kiosk]="false">
</upp-textarea>
</div>
</upp-form-section>
<!-- Warning -->
<upp-form-warning
*ngIf="showWarning"
title="Price is below cost"
subtitle="Review the price before publishing."
icon="warning-outline"
[lines]="warningLines">
</upp-form-warning>
<!-- Action bar -->
<upp-form-buttonbar>
<upp-form-barbutton [active]="true">
<ion-button size="small" color="tertiary" (click)="onPreview()">
<ion-icon name="eye-outline" slot="start"></ion-icon>
Preview
</ion-button>
</upp-form-barbutton>
<upp-form-barbutton [active]="true">
<ion-button size="small" color="success" (click)="onPublish()">
<ion-icon name="cloud-upload-outline" slot="start"></ion-icon>
Publish
</ion-button>
</upp-form-barbutton>
</upp-form-buttonbar>
<!-- Main buttons -->
<upp-form-buttons [float]="buttonFloat">
<ion-button color="primary" (click)="onSave()">
<ion-icon name="save-outline" slot="start"></ion-icon>
Save
</ion-button>
<ion-button color="medium" (click)="onCancel()">
<ion-icon name="close-outline" slot="start"></ion-icon>
Cancel
</ion-button>
</upp-form-buttons>
</upp-form>
</div>
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'demo-upp-form',
templateUrl: './demo-upp-form.html',
styleUrls: ['../demo-common.scss', './demo-upp-form.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DemoUppFormComponent implements OnInit {
form!: FormGroup;
sectionFill: 'clear' | 'solid' = 'solid';
showWarning = true;
buttonFloat: 'top' | 'bottom' = 'bottom';
warningLines: 'solid' | 'none' = 'solid';
constructor(private change: ChangeDetectorRef) {
}
ngOnInit() {
this.form = new FormGroup({
name: new FormControl('', [Validators.required]),
price: new FormControl(''),
description: new FormControl(''),
});
}
toggleSectionFill() {
this.sectionFill = this.sectionFill === 'solid' ? 'clear' : 'solid';
this.change.markForCheck();
}
toggleWarning() {
this.showWarning = !this.showWarning;
this.change.markForCheck();
}
toggleButtonFloat() {
this.buttonFloat = this.buttonFloat === 'bottom' ? 'top' : 'bottom';
this.change.markForCheck();
}
onPreview() {
console.log('Preview:', this.form.value);
}
onPublish() {
console.log('Publish:', this.form.value);
}
onSave() {
console.log('Save:', this.form.value);
}
onCancel() {
this.form.reset();
this.change.markForCheck();
}
}
:host {
display: block;
padding: 16px;
}
API Reference
upp-form
Selector: upp-form
The root form container. Wraps content inside a <form> tag bound to a reactive FormGroup.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | formGroup: FormGroup | null | null | The Angular reactive FormGroup to bind. If null, an internal empty FormGroup is used so the <form> tag always has a valid binding. |
upp-form-section
Selector: upp-form-section
A visual section within the form, with an optional header (title, subtitle, icon, image) and a fill style.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | title: string | null | null | Section title displayed in the header. |
@Input() | subtitle: string | null | null | Subtitle displayed below the title. |
@Input() | icon: string | null | null | Ionic icon name shown in the section header. |
@Input() | image: string | null | null | Image URL shown in the section header. |
@Input() | fill: 'clear' | 'solid' | 'solid' | Background fill style. 'solid' applies a filled background; 'clear' renders transparent. Applied as a CSS host class. |
upp-form-action
Selector: upp-form-action
An inline action row inside the form, typically used as a clickable section header or navigation item.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | title: string | null | null | Action title text. |
@Input() | subtitle: string | null | null | Subtitle text. |
@Input() | icon: string | null | null | Ionic icon name. |
@Input() | color: string | null | null | Ionic color for the action row. |
@Input() | slot: 'start' | 'end' | 'start' | Icon slot position. |
upp-form-warning
Selector: upp-form-warning
A warning banner that fades in after a short delay. Used to display validation messages or important notices inside the form.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | title: string | null | null | Warning title text. |
@Input() | subtitle: string | null | null | Warning subtitle / description. |
@Input() | icon: string | null | null | Ionic icon name. |
@Input() | color: string | null | null | Ionic color for the warning. |
@Input() | lines: 'solid' | 'none' | 'solid' | Border style. 'solid' adds visible borders; 'none' removes them. Applied as a CSS host class. |
@Input() | slot: 'start' | 'end' | 'start' | Icon slot position. |
Behavior: The warning is initially hidden and becomes visible after a 100ms delay via the Visible property, enabling a fade-in animation.
upp-form-image
Selector: upp-form-image
A content projection slot for placing an image at the top of a form.
upp-form-buttonbar
Selector: upp-form-buttonbar
A horizontal bar for inline action buttons (e.g., "Add Item", "Duplicate"). Contains upp-form-barbutton children.
upp-form-barbutton
Selector: upp-form-barbutton
An individual button slot inside upp-form-buttonbar.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | active: boolean | true | Whether the button is active/enabled. |
upp-form-buttons
Selector: upp-form-buttons
The primary action button area (save, cancel, etc.). Supports floating at the top or bottom of the form.
| Type | Name | Default | Description |
|---|---|---|---|
@Input() | float: 'top' | 'bottom' | 'bottom' | Vertical position of the button area. Applied as a CSS host class (--top or --bottom). |
OnForm Interface
An interface for views that use the form system. Not a component, but a contract for form-hosting views.
| Method | Returns | Description |
|---|---|---|
CanAccept() | boolean[] | Returns [canAccept, inconclusive]. First value indicates form validity; second indicates if further checks are needed. |
OnApply() | Promise<boolean> | Handles apply/save logic. Resolves to true if inconclusive (continue chain). |
OnAccept() | Promise<boolean> | Handles acceptance logic. Resolves to true if inconclusive. |
OnCancel() | Promise<boolean> | Handles cancellation. Resolves to true if inconclusive. |