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!