Widget Customization Guide
Complete guide to customizing the Waitlist Widget embed widget. 🎨
Table of Contents
- Configuration Options
- CSS Class Structure
- Custom Styling Examples
- JavaScript Callbacks
- Accessibility
- Advanced Customization
Configuration Options
Basic widget initialization:
WaitlistWidget.init({
apiKey: 'YOUR_API_KEY', // Required
containerId: 'waitlist-widget', // Required
// Optional configuration
placeholder: 'Enter your email...',
buttonText: 'Join Waitlist',
successMessage: 'Thanks for joining!',
errorMessage: 'Something went wrong. Please try again.',
// Styling options
theme: 'light', // 'light' | 'dark' | 'auto'
primaryColor: '#3b82f6',
borderRadius: '8px',
// Callbacks
onSuccess: (data) => {},
onError: (error) => {},
onSubmit: () => {}
});Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | Required | Your project's API key |
containerId | string | Required | ID of the container element |
placeholder | string | "Enter your email..." | Input placeholder text |
buttonText | string | "Join Waitlist" | Submit button text |
successMessage | string | "Thanks for joining our waitlist!" | Success notification text |
errorMessage | string | "Something went wrong. Please try again." | Generic error message |
theme | 'light' | 'dark' | 'auto' | 'light' | Color theme |
primaryColor | string | '#3b82f6' | Primary accent color (hex/rgb) |
borderRadius | string | '8px' | Border radius for inputs/buttons |
onSuccess | function | null | Callback on successful submission |
onError | function | null | Callback on error |
onSubmit | function | null | Callback before submission |
CSS Class Structure
The widget uses these CSS classes for styling:
<div id="waitlist-widget" class="waitlist-container">
<form class="waitlist-form">
<div class="waitlist-input-wrapper">
<input
type="email"
class="waitlist-input"
placeholder="Enter your email..."
/>
<span class="waitlist-input-icon">📧</span>
</div>
<button type="submit" class="waitlist-button">
<span class="waitlist-button-text">Join Waitlist</span>
<span class="waitlist-button-loading" style="display: none;">⏳</span>
</button>
<div class="waitlist-message waitlist-message-success" style="display: none;">
Thanks for joining our waitlist!
</div>
<div class="waitlist-message waitlist-message-error" style="display: none;">
Something went wrong. Please try again.
</div>
</form>
</div>Class Reference
| Class | Description |
|---|---|
.waitlist-container | Main wrapper div |
.waitlist-form | Form element |
.waitlist-input-wrapper | Input wrapper (for icons, etc.) |
.waitlist-input | Email input field |
.waitlist-input-icon | Input icon (optional) |
.waitlist-button | Submit button |
.waitlist-button-text | Button text span |
.waitlist-button-loading | Loading indicator (shown during submit) |
.waitlist-message | Message container (success/error) |
.waitlist-message-success | Success message styling |
.waitlist-message-error | Error message styling |
State Classes
| Class | Applied When |
|---|---|
.waitlist-form--loading | Form is submitting |
.waitlist-form--success | Successful submission |
.waitlist-form--error | Error occurred |
.waitlist-input--error | Input validation failed |
.waitlist-button--disabled | Button is disabled |
Custom Styling Examples
Example 1: Gradient Theme
<style>
/* Gradient background theme */
.waitlist-form {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 2rem;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.waitlist-input {
width: 100%;
padding: 14px 16px;
font-size: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
background: rgba(255, 255, 255, 0.9);
color: #1a202c;
transition: all 0.3s ease;
}
.waitlist-input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.8);
background: #ffffff;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.2);
}
.waitlist-button {
width: 100%;
margin-top: 12px;
padding: 14px 24px;
font-size: 16px;
font-weight: 600;
color: #764ba2;
background: #ffffff;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.waitlist-button:hover {
background: #f7fafc;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.waitlist-message-success {
margin-top: 12px;
padding: 12px;
color: #ffffff;
background: rgba(72, 187, 120, 0.3);
border-radius: 8px;
text-align: center;
}
</style>Example 2: Minimal Dark Theme
<style>
/* Minimal dark theme */
.waitlist-form {
background: #1a1a1a;
padding: 1.5rem;
border-radius: 8px;
border: 1px solid #333;
}
.waitlist-input {
width: 100%;
padding: 12px 16px;
font-size: 15px;
color: #ffffff;
background: #2a2a2a;
border: 1px solid #444;
border-radius: 6px;
transition: border-color 0.2s ease;
}
.waitlist-input:focus {
outline: none;
border-color: #4ade80;
}
.waitlist-input::placeholder {
color: #888;
}
.waitlist-button {
width: 100%;
margin-top: 10px;
padding: 12px;
font-size: 15px;
font-weight: 500;
color: #1a1a1a;
background: #4ade80;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s ease;
}
.waitlist-button:hover {
background: #22c55e;
}
.waitlist-button:disabled {
background: #444;
color: #888;
cursor: not-allowed;
}
.waitlist-message {
margin-top: 10px;
padding: 10px;
font-size: 14px;
border-radius: 6px;
text-align: center;
}
.waitlist-message-success {
color: #4ade80;
background: rgba(74, 222, 128, 0.1);
border: 1px solid #4ade80;
}
.waitlist-message-error {
color: #f87171;
background: rgba(248, 113, 113, 0.1);
border: 1px solid #f87171;
}
</style>Example 3: Modern Glassmorphism
<style>
/* Glassmorphism theme */
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.waitlist-form {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 2rem;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
.waitlist-input {
width: 100%;
padding: 14px 16px;
font-size: 16px;
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
transition: all 0.3s ease;
}
.waitlist-input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.waitlist-input:focus {
outline: none;
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
}
.waitlist-button {
width: 100%;
margin-top: 12px;
padding: 14px;
font-size: 16px;
font-weight: 600;
color: #667eea;
background: rgba(255, 255, 255, 0.9);
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.waitlist-button:hover {
background: #ffffff;
transform: translateY(-2px);
}
.waitlist-message-success {
margin-top: 12px;
padding: 12px;
color: #ffffff;
background: rgba(72, 187, 120, 0.2);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
text-align: center;
}
</style>Example 4: Inline/Horizontal Layout
<style>
/* Horizontal inline form */
.waitlist-form {
display: flex;
gap: 8px;
align-items: center;
padding: 0;
}
.waitlist-input {
flex: 1;
padding: 12px 16px;
font-size: 15px;
border: 2px solid #e5e7eb;
border-radius: 8px;
transition: border-color 0.2s ease;
}
.waitlist-input:focus {
outline: none;
border-color: #3b82f6;
}
.waitlist-button {
flex-shrink: 0;
padding: 12px 24px;
font-size: 15px;
font-weight: 600;
color: #ffffff;
background: #3b82f6;
border: none;
border-radius: 8px;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s ease;
}
.waitlist-button:hover {
background: #2563eb;
}
.waitlist-message {
width: 100%;
margin-top: 8px;
padding: 10px;
font-size: 14px;
border-radius: 6px;
text-align: center;
}
.waitlist-message-success {
color: #059669;
background: #d1fae5;
}
.waitlist-message-error {
color: #dc2626;
background: #fee2e2;
}
/* Mobile responsive */
@media (max-width: 640px) {
.waitlist-form {
flex-direction: column;
}
.waitlist-button {
width: 100%;
}
}
</style>JavaScript Callbacks
onSuccess Callback
Called when a user successfully subscribes.
WaitlistWidget.init({
apiKey: 'YOUR_API_KEY',
containerId: 'waitlist-widget',
onSuccess: (data) => {
console.log('User subscribed!', data);
// Example: Track with analytics
if (window.gtag) {
gtag('event', 'waitlist_signup', {
email: data.subscriber.email
});
}
// Example: Show custom modal
showThankYouModal();
// Example: Redirect to thank you page
setTimeout(() => {
window.location.href = '/thank-you';
}, 2000);
}
});Callback Data:
{
success: true,
subscriber: {
id: string,
email: string,
subscribedAt: string
},
message: string
}onError Callback
Called when an error occurs (validation, network, duplicate email, etc.).
WaitlistWidget.init({
apiKey: 'YOUR_API_KEY',
containerId: 'waitlist-widget',
onError: (error) => {
console.error('Subscription failed:', error);
// Handle specific error codes
switch (error.code) {
case 'EMAIL_ALREADY_SUBSCRIBED':
alert('You\'re already on the waitlist!');
break;
case 'VALIDATION_ERROR':
alert('Please enter a valid email address.');
break;
case 'RATE_LIMIT_EXCEEDED':
alert('Too many attempts. Please try again in a minute.');
break;
default:
alert('Something went wrong. Please try again later.');
}
// Example: Track error with analytics
if (window.gtag) {
gtag('event', 'waitlist_error', {
error_code: error.code,
error_message: error.message
});
}
}
});Error Data:
{
success: false,
error: {
code: string,
message: string,
details?: any
}
}Common Error Codes:
| Code | Description | Action |
|---|---|---|
EMAIL_ALREADY_SUBSCRIBED | Email already exists | Show "Already subscribed" message |
VALIDATION_ERROR | Invalid email format | Show validation error |
RATE_LIMIT_EXCEEDED | Too many requests | Ask user to wait |
INVALID_API_KEY | API key is invalid | Check configuration |
NETWORK_ERROR | Connection failed | Check internet connection |
onSubmit Callback
Called before the form is submitted. Return false to cancel submission.
WaitlistWidget.init({
apiKey: 'YOUR_API_KEY',
containerId: 'waitlist-widget',
onSubmit: (email) => {
console.log('About to submit:', email);
// Example: Custom validation
if (email.endsWith('@competitor.com')) {
alert('Sorry, competitor emails are not allowed.');
return false; // Cancel submission
}
// Example: Confirm before submitting
const confirmed = confirm(`Join waitlist with ${email}?`);
return confirmed; // true = continue, false = cancel
}
});Complete Callback Example
WaitlistWidget.init({
apiKey: 'wls_live_abc123...',
containerId: 'waitlist-widget',
// Styling
theme: 'dark',
primaryColor: '#10b981',
buttonText: 'Get Early Access',
// Callbacks
onSubmit: (email) => {
console.log('Submitting:', email);
// Show loading indicator
document.getElementById('custom-loader').style.display = 'block';
return true; // Continue submission
},
onSuccess: (data) => {
console.log('Success!', data);
// Hide loading indicator
document.getElementById('custom-loader').style.display = 'none';
// Track conversion
if (window.gtag) {
gtag('event', 'conversion', {
send_to: 'AW-CONVERSION_ID/CONVERSION_LABEL'
});
}
// Store in localStorage
localStorage.setItem('waitlist_joined', 'true');
localStorage.setItem('waitlist_email', data.subscriber.email);
// Redirect after 3 seconds
setTimeout(() => {
window.location.href = '/thank-you?email=' + encodeURIComponent(data.subscriber.email);
}, 3000);
},
onError: (error) => {
console.error('Error:', error);
// Hide loading indicator
document.getElementById('custom-loader').style.display = 'none';
// Custom error handling
let message = 'Something went wrong. Please try again.';
if (error.code === 'EMAIL_ALREADY_SUBSCRIBED') {
message = 'Great! You\'re already on our waitlist. Check your email for updates.';
// Treat as success
localStorage.setItem('waitlist_joined', 'true');
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
message = 'Whoa, slow down! Please wait a minute before trying again.';
}
// Show custom notification
showNotification(message, error.code === 'EMAIL_ALREADY_SUBSCRIBED' ? 'success' : 'error');
// Track error
if (window.gtag) {
gtag('event', 'exception', {
description: error.code,
fatal: false
});
}
}
});Accessibility
The widget is designed with accessibility in mind:
Built-in Features
- ✅ Semantic HTML: Proper form elements
- ✅ Keyboard navigation: Tab, Enter to submit
- ✅ ARIA labels: Screen reader support
- ✅ Focus management: Clear focus indicators
- ✅ Error announcements: Screen reader feedback
- ✅ High contrast: WCAG AA compliant colors
Enhancing Accessibility
<!-- Add descriptive label -->
<label for="waitlist-widget" class="sr-only">
Join our waitlist to get early access
</label>
<div id="waitlist-widget"></div>
<style>
/* Screen reader only class */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus indicators */
.waitlist-input:focus,
.waitlist-button:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* High contrast mode */
@media (prefers-contrast: high) {
.waitlist-input {
border-width: 2px;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.waitlist-button,
.waitlist-input {
transition: none;
}
}
</style>Advanced Customization
Custom HTML Structure
If you need complete control, you can build your own form and use the API directly:
<form id="custom-waitlist-form">
<input type="email" id="custom-email" placeholder="Your email" required />
<button type="submit">Join Waitlist</button>
<div id="custom-message"></div>
</form>
<script>
document.getElementById('custom-waitlist-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('custom-email').value;
const messageDiv = document.getElementById('custom-message');
try {
const response = await fetch('https://api.waitlistwidget.com/api/public/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
apiKey: 'YOUR_API_KEY'
})
});
const data = await response.json();
if (data.success) {
messageDiv.textContent = 'Thanks for joining!';
messageDiv.style.color = 'green';
} else {
messageDiv.textContent = data.error.message;
messageDiv.style.color = 'red';
}
} catch (error) {
messageDiv.textContent = 'Network error. Please try again.';
messageDiv.style.color = 'red';
}
});
</script>Programmatic Control
// Initialize widget
const widget = WaitlistWidget.init({
apiKey: 'YOUR_API_KEY',
containerId: 'waitlist-widget'
});
// Submit programmatically
widget.submit('user@example.com');
// Reset form
widget.reset();
// Destroy widget
widget.destroy();
// Update configuration
widget.updateConfig({
buttonText: 'Get Early Access',
primaryColor: '#10b981'
});Troubleshooting
Widget not rendering?
- Check container ID matches
- Ensure script is loaded (
window.WaitlistWidgetexists) - Check browser console for errors
- Verify API key is correct
Styles not applying?
- Check CSS specificity (use
!importantif needed) - Ensure styles load after widget script
- Inspect element to verify class names
- Check for conflicting global styles
Mobile responsiveness issues?
/* Make widget responsive */
.waitlist-form {
max-width: 100%;
width: 100%;
}
.waitlist-input,
.waitlist-button {
width: 100%;
box-sizing: border-box;
}
@media (max-width: 640px) {
.waitlist-form {
padding: 1rem;
}
.waitlist-input,
.waitlist-button {
font-size: 16px; /* Prevent zoom on iOS */
}
}Theme Examples
Want complete theme examples? Contact us at support@waitlistwidget.com for ready-to-use templates:
- ✅ Gradient Theme - Modern gradient backgrounds
- ✅ Dark Theme - Perfect for dark mode sites
- ✅ Glassmorphism - Trendy frosted glass effect
- ✅ Inline Layout - Compact single-line design
All themes include full HTML/CSS code and are production-ready!
Next Steps
- API Documentation: Backend integration
- Webhooks: Automate workflows
- FAQ: Common questions
Need help? Email us at support@waitlistwidget.com - we typically respond within 24 hours!