Modal Plugin
Display experiences in overlay dialogs with backdrop, focus management, and rich content.
Features
- Multiple sizes -
sm,md,lg,fullscreen, orauto - Mobile-optimized - Automatic fullscreen on mobile for
lgsize - Hero images - Add visual impact with top images
- Animations -
fade,slide-up, or none - Positioning -
centerorbottomplacement - Form support - Built-in email capture with validation
- Accessibility - Focus trap, ARIA attributes, keyboard support (Escape)
- Dismissal - Configurable close button and backdrop dismiss
- CSS Variables - Full theming support
Basic Usage
import { experiences } from '@prosdevlab/experience-sdk';
experiences.register('welcome-modal', {
type: 'modal',
content: {
title: 'Welcome!',
message: 'Get 20% off your first purchase.',
buttons: [
{ text: 'Shop Now', variant: 'primary', url: '/shop' },
{ text: 'Maybe Later', variant: 'secondary', dismiss: true }
]
}
});Content Options
Basic Modal
{
type: 'modal',
content: {
// Text content
title: 'Flash Sale!',
message: 'Limited time offer - <strong>50% off</strong> everything.',
// Buttons
buttons: [
{
text: 'Shop Now',
variant: 'primary',
url: '/shop',
dismiss: true // Close modal on click
},
{
text: 'Dismiss',
variant: 'link',
dismiss: true
}
],
// Size and behavior
size: 'md', // sm | md | lg | fullscreen | auto
position: 'center', // center | bottom
animation: 'fade', // fade | slide-up | none
dismissable: true, // Show close button
backdropDismiss: true, // Click backdrop to close
// Hero image (optional)
image: {
src: '/images/hero.jpg',
alt: 'Flash Sale',
maxHeight: 300
},
// Custom styling
className: 'custom-modal',
style: { borderRadius: '16px' }
}
}Modal with Form
Capture emails, phone numbers, or custom data with built-in validation.
{
type: 'modal',
content: {
title: 'Stay Updated',
message: 'Subscribe to our newsletter for exclusive offers.',
form: {
fields: [
{
name: 'email',
label: 'Email Address',
type: 'email',
placeholder: 'you@example.com',
required: true,
errorMessage: 'Please enter a valid email'
}
],
submitButton: {
text: 'Subscribe',
variant: 'primary',
loadingText: 'Subscribing...'
},
// Optional: Custom validation
validate: (data) => {
if (data.email.includes('test')) {
return {
valid: false,
errors: { email: 'Test emails not allowed' }
};
}
return { valid: true };
},
// Success state
successState: {
title: '✓ Subscribed!',
message: 'Check your inbox for a confirmation email.',
buttons: [
{ text: 'Continue', variant: 'primary', dismiss: true }
]
},
// Error state
errorState: {
title: '✗ Something went wrong',
message: 'Please try again later.',
buttons: [
{ text: 'Try Again', variant: 'primary', action: 'reset' },
{ text: 'Cancel', variant: 'secondary', dismiss: true }
]
}
}
}
}Form Field Types
{
fields: [
{ name: 'email', type: 'email', required: true },
{ name: 'name', type: 'text', placeholder: 'Your name' },
{ name: 'phone', type: 'tel', pattern: '^[0-9]{10}$' },
{ name: 'website', type: 'url' },
{ name: 'age', type: 'number' },
{
name: 'comments',
type: 'textarea',
placeholder: 'Tell us more...'
}
]
}Size Variants
// Small modal (max-width: 400px)
{ size: 'sm' }
// Medium modal (max-width: 600px) - Default
{ size: 'md' }
// Large modal (max-width: 800px)
{ size: 'lg' }
// Full viewport (100vw x 100vh)
{ size: 'fullscreen' }
// Auto-size based on content
{ size: 'auto' }Mobile behavior: lg size automatically becomes fullscreen on mobile (< 768px).
Animations
// Fade in (default)
{ animation: 'fade' }
// Slide up from bottom
{ animation: 'slide-up' }
// No animation
{ animation: 'none' }Hero Images
Add visual impact to modals:
{
image: {
src: '/promo-banner.jpg',
alt: 'Summer Sale',
maxHeight: 250 // Max height in pixels
}
}Events
Listen to modal lifecycle events:
import { experiences } from '@prosdevlab/experience-sdk';
// Modal shown
experiences.on('experiences:shown', (event) => {
if (event.type === 'modal') {
console.log('Modal shown:', event.experienceId);
}
});
// Button clicked
experiences.on('experiences:action', (event) => {
console.log('Button clicked:', event.action, event.text);
});
// Modal dismissed
experiences.on('experiences:dismissed', (event) => {
console.log('Modal dismissed:', event.experienceId);
});
// Form events
experiences.on('experiences:modal:form:change', (event) => {
console.log('Form field changed:', event.field, event.value);
});
experiences.on('experiences:modal:form:validation', (event) => {
if (!event.valid) {
console.log('Validation errors:', event.errors);
}
});
experiences.on('experiences:modal:form:submit', (event) => {
console.log('Form submitted:', event.data);
// Submit to your backend
fetch('/api/subscribe', {
method: 'POST',
body: JSON.stringify(event.data)
})
.then(() => {
// Show success state
experiences.modal.showFormState(event.experienceId, 'success');
})
.catch(() => {
// Show error state
experiences.modal.showFormState(event.experienceId, 'error');
});
});API Methods
import { experiences } from '@prosdevlab/experience-sdk';
// Show a modal programmatically
experiences.modal.show(experience);
// Hide a specific modal
experiences.modal.hide('welcome-modal');
// Hide all modals
experiences.modal.hideAll();
// Check if any modal is showing
experiences.modal.isShowing(); // true/false
// Check if specific modal is showing
experiences.modal.isShowing('welcome-modal'); // true/false
// Form methods
experiences.modal.showFormState('newsletter', 'success');
experiences.modal.resetForm('newsletter');
const formData = experiences.modal.getFormData('newsletter');CSS Variables
Customize modal appearance:
:root {
/* Modal container */
--xp-modal-z-index: 10001;
/* Backdrop */
--xp-modal-backdrop-bg: rgba(0, 0, 0, 0.5);
/* Dialog */
--xp-modal-dialog-bg: #ffffff;
--xp-modal-dialog-border-radius: 8px;
--xp-modal-dialog-box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--xp-modal-dialog-padding: 24px;
/* Close button */
--xp-modal-close-color: #666;
--xp-modal-close-hover-color: #111;
/* Title */
--xp-modal-title-font-size: 20px;
--xp-modal-title-font-weight: 600;
--xp-modal-title-color: #111;
/* Message */
--xp-modal-message-font-size: 14px;
--xp-modal-message-color: #444;
/* Buttons */
--xp-modal-button-primary-bg: #2563eb;
--xp-modal-button-primary-color: #ffffff;
--xp-modal-button-secondary-bg: #f3f4f6;
--xp-modal-button-secondary-color: #374151;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--xp-modal-dialog-bg: #1f2937;
--xp-modal-title-color: #f9fafb;
--xp-modal-message-color: #d1d5db;
}
}Examples
Email Signup Modal
experiences.register('newsletter', {
type: 'modal',
content: {
title: 'Join Our Newsletter',
message: 'Get 10% off your first order.',
size: 'sm',
animation: 'slide-up',
form: {
fields: [
{
name: 'email',
type: 'email',
placeholder: 'you@example.com',
required: true
}
],
submitButton: {
text: 'Get Discount',
variant: 'primary'
},
successState: {
title: '✓ Check Your Inbox',
message: 'Your discount code is on its way!'
}
}
},
targeting: {
custom: (context) => context.triggers.exitIntent
}
});Product Launch Modal
experiences.register('product-launch', {
type: 'modal',
content: {
title: 'Introducing Pro Plan',
message: 'Advanced features for power users.',
size: 'lg',
image: {
src: '/product-hero.jpg',
alt: 'Pro Plan',
maxHeight: 300
},
buttons: [
{ text: 'Learn More', variant: 'primary', url: '/pro' },
{ text: 'Remind Me Later', variant: 'link', dismiss: true }
]
}
});Cookie Consent Modal
experiences.register('cookie-consent', {
type: 'modal',
content: {
title: 'Cookie Settings',
message: 'We use cookies to improve your experience.',
size: 'md',
position: 'bottom',
dismissable: false, // Require user action
backdropDismiss: false,
buttons: [
{
text: 'Accept All',
variant: 'primary',
action: 'accept',
dismiss: true
},
{
text: 'Customize',
variant: 'secondary',
url: '/cookies'
}
]
}
});Accessibility
The modal plugin follows WAI-ARIA best practices:
- Focus management - Traps focus within modal, restores on close
- Keyboard support - Escape key closes modal (if dismissable)
- ARIA attributes -
role="dialog",aria-modal="true",aria-labelledby - Screen readers - Proper labeling and announcements
Browser Support
- Chrome, Firefox, Safari, Edge (last 2 versions)
- Mobile: iOS Safari, Chrome Android
- Requires ES2024+ support (or transpilation)
Next Steps
- Exit Intent Plugin - Trigger modals on exit
- Frequency Plugin - Cap modal impressions
- Events Reference - Full event documentation