sweetalert-plus-plus
v1.0.0
Published
Enterprise-grade, accessible, customizable modal and alert library with 30+ form field types, theme system, and advanced visual effects
Maintainers
Readme
Sweet Alert++
A modern, accessible, lightweight modal and alert library. The better alternative to SweetAlert2.
Features
- Lightweight: Core is ~5KB gzipped, full library ~12KB (vs SweetAlert2's ~15KB)
- Tree-shakeable: Import only what you need
- Accessible: Full WCAG 2.1 AA compliance, proper focus management, screen reader support
- Modern: CSS custom properties, Web Animations API, ESM-first
- Framework Ready: First-class React and Vue 3 adapters
- TypeScript: Complete type definitions
- No Dependencies: Zero runtime dependencies
- SSR Safe: Works with Next.js, Nuxt, and other SSR frameworks
- Dark Mode: Automatic dark mode support via CSS custom properties
- Reduced Motion: Respects
prefers-reduced-motion
Installation
npm install sweet-alert-plus-plus
# or
yarn add sweet-alert-plus-plus
# or
pnpm add sweet-alert-plus-plusQuick Start
import { alert, confirm, prompt } from 'sweet-alert-plus-plus';
import 'sweet-alert-plus-plus/styles';
// Simple alert
await alert('Hello World!');
// Confirmation dialog
const confirmed = await confirm('Are you sure?');
// Prompt for input
const name = await prompt('What is your name?');API Overview
Core Functions
import { modal, alert, confirm, prompt, toast } from 'sweet-alert-plus-plus';
// Full modal with all options
const result = await modal({
title: 'Custom Modal',
text: 'This is a custom modal with many options',
icon: 'info',
buttons: {
confirm: 'Proceed',
cancel: 'Go Back',
},
input: {
type: 'text',
placeholder: 'Enter something...',
},
});
// Simple alerts
await alert('Something happened!');
await alert({ title: 'Error', text: 'Something went wrong', icon: 'error' });
// Confirmations
const yes = await confirm('Delete this item?');
const result = await confirm({
title: 'Are you sure?',
text: 'This cannot be undone',
icon: 'warning',
confirmText: 'Delete',
cancelText: 'Keep',
});
// Prompts
const value = await prompt('Enter your email');
const email = await prompt({
title: 'Subscribe',
inputType: 'email',
placeholder: '[email protected]',
validate: (v) => v.includes('@') || 'Invalid email',
});
// Toast notifications
toast('Quick message');
toast({ text: 'Success!', icon: 'success' });Result Object
const result = await modal({ title: 'Example' });
result.confirmed // true if user clicked confirm
result.denied // true if user clicked deny
result.dismissed // true if dismissed (ESC, backdrop, etc.)
result.dismissReason // 'backdrop' | 'escape' | 'close' | 'timer' | 'cancel'
result.value // Input value (if input was used)Full Options Reference
interface ModalOptions {
// Content
title?: string | HTMLElement;
text?: string;
html?: string | HTMLElement; // Sanitized by default
footer?: string | HTMLElement;
icon?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'loading';
iconColor?: string;
image?: { src: string; alt?: string; width?: number; height?: number };
// Buttons
buttons?: {
confirm?: ButtonConfig | string | boolean;
deny?: ButtonConfig | string | boolean;
cancel?: ButtonConfig | string | boolean;
layout?: 'horizontal' | 'vertical' | 'space-between';
};
// Input
input?: {
type: 'text' | 'email' | 'password' | 'number' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'file';
label?: string;
placeholder?: string;
value?: any;
validate?: (value: any) => string | boolean | Promise<string | boolean>;
options?: Array<{ value: string; label: string }>; // For select/radio/checkbox
required?: boolean;
};
// Layout
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'auto';
position?: 'center' | 'top' | 'bottom' | 'top-start' | 'top-end';
width?: number | string;
// Backdrop
backdrop?: boolean | 'static';
closeOnBackdrop?: boolean;
// Behavior
showCloseButton?: boolean;
timer?: number;
timerProgressBar?: boolean;
pauseTimerOnHover?: boolean;
// Animation
animation?: 'fade' | 'scale' | 'slide-up' | 'slide-down' | false;
// Accessibility
a11y?: {
role?: 'dialog' | 'alertdialog';
ariaLabel?: string;
closeOnEscape?: boolean;
trapFocus?: boolean;
returnFocus?: boolean;
};
// Lifecycle Hooks
hooks?: {
onBeforeOpen?: (modal) => void | boolean;
onOpen?: (modal) => void;
onAfterOpen?: (modal) => void;
onBeforeClose?: (modal, result) => void | boolean;
onClose?: (result) => void;
onBeforeConfirm?: (value) => any | Promise<any>;
onInputChange?: (value, modal) => void;
};
}Theming with CSS Custom Properties
:root {
/* Colors */
--modal-color-primary: #3b82f6;
--modal-bg: #ffffff;
--modal-text: #111827;
/* Sizing */
--modal-width-md: 28rem;
--modal-radius: 0.75rem;
/* Buttons */
--modal-btn-primary-bg: var(--modal-color-primary);
--modal-btn-radius: 0.5rem;
/* Icons */
--modal-icon-success: #10b981;
--modal-icon-error: #ef4444;
}
/* Dark mode */
[data-theme="dark"] {
--modal-bg: #1f2937;
--modal-text: #f3f4f6;
}Toast Notifications
import { toast, toastSuccess, toastError, toastPromise } from 'sweet-alert-plus-plus';
// Simple toast
toast('Hello!');
// With options
toast({
title: 'New Message',
text: 'You have a new message',
icon: 'info',
position: 'top-end',
duration: 5000,
progressBar: true,
pauseOnHover: true,
closeOnClick: true,
draggable: true,
});
// Convenience methods
toastSuccess('Saved!');
toastError('Failed to save');
// Promise toast
const result = await toastPromise(
fetch('/api/save'),
{
loading: 'Saving...',
success: 'Saved successfully!',
error: (err) => `Error: ${err.message}`,
}
);Queue System
import { queue, createQueue } from 'sweet-alert-plus-plus';
// Add to queue
await queue.enqueue({ title: 'Step 1' });
await queue.enqueue({ title: 'Step 2' });
// Configure queue
queue.configure({
mode: 'sequential', // or 'stack'
delay: 100,
showProgress: true,
});
// Fluent builder
const results = await createQueue()
.add({ title: 'First', text: 'First modal' })
.add({ title: 'Second', text: 'Second modal' })
.add({ title: 'Third', text: 'Third modal' })
.configure({ showProgress: true })
.run();React Integration
import { ModalProvider, useModal, useConfirm, useToast } from 'sweet-alert-plus-plus/react';
// Wrap your app
function App() {
return (
<ModalProvider>
<MyComponent />
</ModalProvider>
);
}
// Use hooks
function MyComponent() {
const { open, close, isOpen } = useModal();
const { confirm } = useConfirm();
const { toast, success, error } = useToast();
async function handleDelete() {
const yes = await confirm('Delete this item?');
if (yes) {
try {
await deleteItem();
success('Deleted!');
} catch (e) {
error('Failed to delete');
}
}
}
return <button onClick={handleDelete}>Delete</button>;
}Vue 3 Integration
<script setup>
import { useModal, useConfirm, useToast } from 'sweet-alert-plus-plus/vue';
const { open, close, isOpen } = useModal();
const { confirm } = useConfirm();
const { toast, success, error } = useToast();
async function handleDelete() {
const yes = await confirm('Delete this item?');
if (yes) {
try {
await deleteItem();
success('Deleted!');
} catch (e) {
error('Failed to delete');
}
}
}
</script>// main.js - Plugin installation
import { createApp } from 'vue';
import { createModalPlugin } from 'sweet-alert-plus-plus/vue';
const app = createApp(App);
app.use(createModalPlugin({
// Global config
}));Multi-step Wizard
import { wizard } from 'sweet-alert-plus-plus';
const result = await wizard({
steps: [
{
title: 'Step 1: Name',
input: { type: 'text', label: 'Your name' },
validate: () => true,
},
{
title: 'Step 2: Email',
input: { type: 'email', label: 'Your email' },
},
{
title: 'Step 3: Confirm',
text: 'Ready to submit?',
},
],
showSteps: true,
allowBack: true,
});
if (result.completed) {
console.log(result.values);
// { 'step-0': 'John', 'step-1': '[email protected]' }
}Accessibility Features
- ARIA Support: Proper
role,aria-modal,aria-labelledby,aria-describedby - Focus Management: Focus trapped within modal, returns to trigger on close
- Keyboard Navigation: Full Tab cycling, ESC to close
- Screen Readers: Live region announcements for dynamic content
- Reduced Motion: Respects
prefers-reduced-motionmedia query - Color Contrast: Default theme meets WCAG AA standards
Security
- HTML Sanitization: All HTML content is sanitized by default
- CSP Friendly: No inline styles or
eval - XSS Prevention: Safe by default, opt-in for unsafe HTML
// Safe (sanitized)
modal({ html: '<b>Bold</b><script>evil()</script>' });
// Result: <b>Bold</b>
// Explicit unsafe (use with caution!)
modal({ html: userContent, allowUnsafeHtml: true });Bundle Sizes
| Entry Point | Minified | Gzipped | |------------|----------|---------| | Core only | ~15KB | ~5KB | | Full library | ~35KB | ~12KB | | React adapter | +4KB | +1.5KB | | Vue adapter | +3KB | +1KB |
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Comparison with SweetAlert2
| Feature | Sweet Alert++ | SweetAlert2 | |---------|--------------|-------------| | Bundle size | ~12KB gzip | ~15KB gzip | | Tree-shaking | Yes | No | | CSS Variables | Yes | Limited | | Dark mode | Automatic | Manual | | React hooks | Built-in | Community | | Vue 3 | Built-in | Community | | Focus trap | Robust | Buggy | | ARIA support | Complete | Partial | | Reduced motion | Yes | No | | TypeScript | Full | Incomplete |
Migration from SweetAlert2
// SweetAlert2
import Swal from 'sweetalert2';
Swal.fire({
title: 'Are you sure?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
});
// Sweet Alert++ equivalent
import { confirm } from 'sweet-alert-plus-plus';
await confirm({
title: 'Are you sure?',
icon: 'warning',
confirmText: 'Yes',
cancelText: 'No',
});License
MIT
