Overview
The Modal component displays content in a dialog overlay that appears above the main application content. Modals are useful for focusing user attention on critical information, forms, or actions without navigating away from the current page.
Basic usage
Import the Modal component and control its visibility with the open property:
import { ModalComponent } from '@flowx/angular-ui-toolkit';
@Component({
selector: 'app-example',
standalone: true,
imports: [ModalComponent],
template: `
<button (click)="openModal()">Open Modal</button>
<flx-modal
[open]="isOpen"
[title]="'Confirmation'"
(close)="closeModal()">
<p>Are you sure you want to proceed?</p>
<div slot="footer">
<button (click)="closeModal()">Cancel</button>
<button (click)="confirm()">Confirm</button>
</div>
</flx-modal>
`
})
export class ExampleComponent {
isOpen = false;
openModal() {
this.isOpen = true;
}
closeModal() {
this.isOpen = false;
}
confirm() {
console.log('Confirmed');
this.closeModal();
}
}
Component selector
Properties
open
boolean
default:"false"
required
Controls whether the modal is visible. Use two-way binding with [(open)] for automatic state management.
The title displayed in the modal header.
size
'small' | 'medium' | 'large' | 'fullscreen'
default:"'medium'"
Controls the width of the modal dialog:
small: 400px max width
medium: 600px max width
large: 900px max width
fullscreen: Full viewport size
When true, clicking the backdrop (overlay) closes the modal.
When true, pressing the Escape key closes the modal.
When true, displays a close button (×) in the modal header.
Alias for closeOnBackdropClick. Controls backdrop click behavior.
When true, prevents the modal from being closed by backdrop clicks or Escape key. User must use explicit close actions.
When true, allows the modal body to scroll when content exceeds the viewport height.
When true, vertically centers the modal in the viewport.
Events
Emitted when the modal is closed by any method (backdrop click, Escape key, or close button).
Emitted when the modal’s open state changes. Useful for two-way binding.
Emitted after the modal has fully opened (animation complete).
Emitted after the modal has fully closed (animation complete).
Content projection
The Modal component supports multiple content areas:
Default content slot
<flx-modal [open]="isOpen" [title]="'Simple Modal'">
<!-- Main modal content -->
<p>This content appears in the modal body</p>
</flx-modal>
Named slots
<flx-modal [open]="isOpen">
<div slot="header">
<h2>Custom Header</h2>
<span class="badge">New</span>
</div>
<p>Modal content</p>
</flx-modal>
Examples
Confirmation dialog
Form modal
Multi-step modal
Fullscreen modal
Async data loading
Use a modal for confirmation dialogs:@Component({
template: `
<button (click)="showDeleteConfirmation()">Delete Item</button>
<flx-modal
[(open)]="showConfirmation"
[title]="'Confirm Deletion'"
[size]="'small'"
[persistent]="true">
<div class="confirmation-content">
<p>Are you sure you want to delete this item?</p>
<p class="warning">This action cannot be undone.</p>
</div>
<div slot="footer">
<button
class="btn-secondary"
(click)="showConfirmation = false">
Cancel
</button>
<button
class="btn-danger"
(click)="deleteItem()">
Delete
</button>
</div>
</flx-modal>
`,
styles: [`
.confirmation-content {
padding: 1rem 0;
}
.warning {
color: var(--warning-color);
font-weight: 500;
}
`]
})
export class DeleteConfirmationComponent {
showConfirmation = false;
showDeleteConfirmation() {
this.showConfirmation = true;
}
deleteItem() {
console.log('Item deleted');
this.showConfirmation = false;
}
}
Display a form in a modal dialog:@Component({
template: `
<button (click)="openForm()">Add User</button>
<flx-modal
[(open)]="isFormOpen"
[title]="'Add New User'"
[size]="'medium'"
[closeOnBackdropClick]="false"
(close)="resetForm()">
<form [formGroup]="userForm">
<div class="form-group">
<label for="name">Name</label>
<input
id="name"
type="text"
formControlName="name"
placeholder="Enter name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input
id="email"
type="email"
formControlName="email"
placeholder="Enter email" />
</div>
<div class="form-group">
<label for="role">Role</label>
<select id="role" formControlName="role">
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
</select>
</div>
</form>
<div slot="footer">
<button
type="button"
(click)="cancelForm()">
Cancel
</button>
<button
type="button"
[disabled]="!userForm.valid"
(click)="submitForm()">
Save User
</button>
</div>
</flx-modal>
`
})
export class UserFormModalComponent {
isFormOpen = false;
userForm: FormGroup;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
role: ['user', Validators.required]
});
}
openForm() {
this.isFormOpen = true;
}
cancelForm() {
this.resetForm();
this.isFormOpen = false;
}
submitForm() {
if (this.userForm.valid) {
console.log('User created:', this.userForm.value);
this.isFormOpen = false;
this.resetForm();
}
}
resetForm() {
this.userForm.reset({ role: 'user' });
}
}
Create a wizard-style modal with multiple steps:@Component({
template: `
<flx-modal
[(open)]="isWizardOpen"
[title]="steps[currentStep].title"
[size]="'large'"
[persistent]="true">
<div class="wizard-content">
<div class="step-indicator">
<span
*ngFor="let step of steps; let i = index"
[class.active]="i === currentStep"
[class.completed]="i < currentStep">
{{ i + 1 }}. {{ step.label }}
</span>
</div>
<div class="step-content">
<div *ngIf="currentStep === 0">
<h3>Step 1: Basic Information</h3>
<!-- Step 1 form fields -->
</div>
<div *ngIf="currentStep === 1">
<h3>Step 2: Configuration</h3>
<!-- Step 2 form fields -->
</div>
<div *ngIf="currentStep === 2">
<h3>Step 3: Review</h3>
<!-- Summary of selections -->
</div>
</div>
</div>
<div slot="footer">
<button
*ngIf="currentStep > 0"
(click)="previousStep()">
Back
</button>
<button
*ngIf="currentStep < steps.length - 1"
(click)="nextStep()">
Next
</button>
<button
*ngIf="currentStep === steps.length - 1"
(click)="complete()">
Complete
</button>
</div>
</flx-modal>
`
})
export class WizardModalComponent {
isWizardOpen = false;
currentStep = 0;
steps = [
{ label: 'Info', title: 'Enter Information' },
{ label: 'Config', title: 'Configure Settings' },
{ label: 'Review', title: 'Review & Submit' }
];
nextStep() {
if (this.currentStep < this.steps.length - 1) {
this.currentStep++;
}
}
previousStep() {
if (this.currentStep > 0) {
this.currentStep--;
}
}
complete() {
console.log('Wizard completed');
this.isWizardOpen = false;
this.currentStep = 0;
}
}
Use a fullscreen modal for complex content:@Component({
template: `
<button (click)="openPreview()">Preview Document</button>
<flx-modal
[(open)]="isPreviewOpen"
[size]="'fullscreen'"
[title]="documentTitle">
<div class="document-viewer">
<iframe
[src]="documentUrl | safe"
frameborder="0">
</iframe>
</div>
<div slot="footer">
<button (click)="downloadDocument()">Download</button>
<button (click)="printDocument()">Print</button>
<button (click)="isPreviewOpen = false">Close</button>
</div>
</flx-modal>
`,
styles: [`
.document-viewer {
height: calc(100vh - 180px);
}
.document-viewer iframe {
width: 100%;
height: 100%;
}
`]
})
export class DocumentPreviewComponent {
isPreviewOpen = false;
documentTitle = 'Annual Report 2024';
documentUrl = '/assets/documents/report.pdf';
openPreview() {
this.isPreviewOpen = true;
}
downloadDocument() {
console.log('Download document');
}
printDocument() {
window.print();
}
}
Load data asynchronously when modal opens:@Component({
template: `
<flx-modal
[(open)]="isOpen"
[title]="'User Details'"
(opened)="loadUserData()">
<div *ngIf="loading" class="loading">
<flx-spinner></flx-spinner>
<p>Loading user data...</p>
</div>
<div *ngIf="!loading && userData" class="user-details">
<h4>{{ userData.name }}</h4>
<p>Email: {{ userData.email }}</p>
<p>Role: {{ userData.role }}</p>
<p>Last login: {{ userData.lastLogin | date }}</p>
</div>
<div *ngIf="error" class="error">
<p>Failed to load user data</p>
</div>
</flx-modal>
`
})
export class UserDetailsModalComponent {
isOpen = false;
loading = false;
userData: any = null;
error = false;
async loadUserData() {
this.loading = true;
this.error = false;
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
this.userData = {
name: 'John Doe',
email: 'john@example.com',
role: 'Administrator',
lastLogin: new Date()
};
} catch (err) {
this.error = true;
} finally {
this.loading = false;
}
}
}
When using persistent modals, always provide a clear way for users to close or cancel the modal, such as a “Cancel” button in the footer.
Accessibility
The Modal component follows accessibility best practices:
- Traps focus within the modal when open
- Returns focus to the trigger element when closed
- Uses proper ARIA attributes (
role="dialog", aria-modal="true")
- Supports keyboard navigation (Tab, Shift+Tab, Escape)
- Announces modal opening to screen readers
- Prevents background scrolling when open
Avoid nesting modals within other modals, as this creates a confusing user experience and accessibility issues. Consider using a multi-step modal or drawer component instead.
Styling
Customize modal appearance using CSS custom properties:
flx-modal {
--modal-backdrop-color: rgba(0, 0, 0, 0.5);
--modal-background: #ffffff;
--modal-border-radius: 8px;
--modal-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
--modal-header-padding: 20px;
--modal-body-padding: 20px;
--modal-footer-padding: 16px 20px;
--modal-max-height: 90vh;
}
Common use cases
- Confirmation dialogs: Confirm destructive actions
- Forms: Collect user input without page navigation
- Alerts and notifications: Display important messages
- Image galleries: Show full-size images or media
- Detail views: Display detailed information about an item
- Wizards: Guide users through multi-step processes
- Document preview: Show documents, PDFs, or other content