upp-dialog
A centered modal shell with a backdrop mask and content projection. It is an embedded alternative to Ionic ModalController: the consumer controls visibility (*ngIf or similar) and projects title, form, and actions inside the panel.
When to Use
Use upp-dialog for short confirmations or compact forms that need a modal overlay without registering a separate Ionic modal. Typical consumers wrap business content (for example account settlement) and listen to OnClose to hide the shell.
The host applies desktop or mobile classes from viewService. On desktop the panel is a centered card; on mobile it fills the viewport. After mount the host reparents to document.body so a fixed overlay always covers the full viewport.
Demo
Source Code
- HTML
- TypeScript
- SCSS
<h2>upp-dialog</h2>
<p class="demo-description">
Centered modal shell with <code>upp-dialog-header</code> / <code>upp-dialog-content</code> /
<code>upp-dialog-footer</code> slots. The host reparents to <code>document.body</code> so the overlay covers the full viewport.
</p>
<div class="demo-controls">
<ion-button size="small" (click)="OnOpen()">Open dialog</ion-button>
<ion-button size="small" fill="outline" (click)="OnToggleBackdrop()">
backdropDismiss: {{ backdropDismiss }}
</ion-button>
</div>
<upp-dialog *ngIf="open" [backdropDismiss]="backdropDismiss" (OnClose)="OnClose()">
<upp-dialog-header class="demo-dialog-header">
<h3>Confirm action</h3>
<ion-button fill="clear" size="small" (click)="OnClose()">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</upp-dialog-header>
<upp-dialog-content class="demo-dialog-body">
<p>Click the backdrop, press Escape, or use Close / Confirm below.</p>
<p *ngFor="let line of scrollLines">{{ line }}</p>
</upp-dialog-content>
<upp-dialog-footer class="demo-dialog-footer">
<ion-button expand="block" fill="outline" (click)="OnClose()">Close</ion-button>
<ion-button expand="block" color="warning" (click)="OnClose()">Confirm</ion-button>
</upp-dialog-footer>
</upp-dialog>
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
@Component({
selector: 'demo-upp-dialog',
templateUrl: './demo-upp-dialog.html',
styleUrls: ['../demo-common.scss', './demo-upp-dialog.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DemoUppDialogComponent {
open = false;
backdropDismiss = true;
/** Extra lines so the content slot scrolls while header/footer stay fixed. */
readonly scrollLines = Array.from({ length: 12 }, (_, index) => `Scrollable line ${index + 1}`);
constructor(private change: ChangeDetectorRef) {}
OnOpen(): void {
this.open = true;
this.change.markForCheck();
}
OnClose(): void {
this.open = false;
this.change.markForCheck();
}
OnToggleBackdrop(): void {
this.backdropDismiss = !this.backdropDismiss;
this.change.markForCheck();
}
}
:host {
display: block;
padding: 16px;
}
.demo-dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid rgba(var(--ion-color-medium-rgb), 0.24);
background: var(--ion-background-color);
h3 {
margin: 0;
font-size: 1.05rem;
font-weight: 600;
}
}
.demo-dialog-body {
padding: 16px 20px;
p {
margin: 0 0 12px;
}
p:last-child {
margin-bottom: 0;
}
}
.demo-dialog-footer {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 16px 16px;
border-top: 1px solid rgba(var(--ion-color-medium-rgb), 0.24);
background: var(--ion-background-color);
}
API Reference
upp-dialog
| Input / Output | Type | Default | Description |
|---|---|---|---|
backdropDismiss | boolean | true | When true, backdrop click, Enter, and Space emit OnClose. |
OnClose | EventEmitter<void> | — | Emitted when the user dismisses the dialog (backdrop when allowed, or Escape). |
Host classes: mobile when viewService.Mobile is true; desktop when viewService.Desktop is true.
Content projection: markup inside <upp-dialog>…</upp-dialog> is rendered in the centered panel. For fixed header/footer with a scrolling body, project into the structural slots:
| Slot | Role |
|---|---|
upp-dialog-header | Fixed top region (flex: 0 0 auto) |
upp-dialog-content | Scrollable body (upp-scrollable; @Input() scrollbar, default 'y') |
upp-dialog-footer | Fixed bottom region (flex: 0 0 auto) |
Visual chrome (colors, typography, padding) stays in the consumer’s classes on those slots. After mount the host reparents to document.body, so set CSS variables with an inline style on <upp-dialog> (not a descendant selector in the caller SCSS).
CSS variables (on :host):
| Variable | Default | Description |
|---|---|---|
--upp-dialog-panel-width | 420px | Preferred width of the desktop panel (min(var, 100%) of the overlay) |
--upp-dialog-panel-inset | 24px | Outer padding around the desktop panel |
--upp-dialog-panel-inset-block | var(--upp-dialog-panel-inset) | Top/bottom overlay padding (desktop) |
--upp-dialog-panel-inset-inline | var(--upp-dialog-panel-inset) | Left/right overlay padding (desktop) |
--upp-dialog-panel-radius | 10px | Border radius on desktop |
--upp-dialog-z-index | 12000 | Stacking order of the overlay |