Modals
Modals are centred overlay dialogs that appear on top of the page with a backdrop. They are not attached to any specific element -- they float in the centre of the viewport and demand the user's attention before they can continue.
Overview
A modal consists of:
- Backdrop overlay -- Dims the entire page behind the modal
- Dialog card -- A centred container with title, body content, optional media, and action buttons
- Close mechanism -- Close button, backdrop click, or Escape key
Modals are ideal for:
- Feature announcements and release notes
- Welcome messages for new users
- Important notifications that require acknowledgement
- Promotional offers and upgrade prompts
- Consent or agreement dialogs
- Survey or feedback collection forms
Creating a Modal
Via the Admin Panel
- Navigate to Content > Modals and click Create Modal.
- Write the modal title and body content.
- Add optional media (image, video).
- Configure action buttons (primary CTA, secondary action, dismiss).
- Set trigger, audience, and scheduling rules.
- Click Publish.
Via the API
All non-tour content types share the same admin endpoint — POST /v1/admin/content — with a content_type discriminator.
POST /v1/admin/content
Content-Type: application/json
X-Api-Key: sk_live_...
{
"content_type": "modal",
"name": "January Release Notes",
"title": "What's New in January",
"body": "<p>We shipped three features you asked for:</p>...",
"media": {
"type": "image",
"src": "https://cdn.example.com/release-jan.png",
"alt": "January release highlights"
},
"primary_cta": {
"text": "See All Changes",
"action": "url",
"url": "/changelog"
},
"dismiss_text": "Got It",
"size": "medium",
"backdrop_click_dismisses": true,
"trigger": { "trigger_type": "first_visit" },
"audience": {
"rules": [
{ "attribute": "role", "operator": "neq", "values": ["viewer"] }
]
}
}Configuration
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Internal name for the modal |
title | string | Yes | Modal heading text |
body | string | Yes | Modal body content (supports safe HTML) |
media | object | No | Embedded image or video |
primary_cta | object | No | Primary call-to-action button |
secondary_cta | object | No | Secondary action button |
dismiss_text | string | No | Dismiss button text (default: "Close") |
size | string | No | Modal width: small, medium, large (default: medium) |
backdrop_click_dismisses | boolean | No | Whether clicking the backdrop closes the modal (default: true) |
show_close_button | boolean | No | Show the X close button in the top-right corner (default: true) |
trigger | object | No | When to show the modal |
audience | object | No | Who should see the modal |
Sizes
| Size | Max Width | Use Case |
|---|---|---|
small | 400px | Quick announcements, confirmations |
medium | 560px | Feature announcements, release notes (default) |
large | 720px | Detailed content with media, onboarding flows |
{ "size": "large" }Action Buttons
Modals support up to two action buttons plus a dismiss action.
Primary CTA
The primary call-to-action is styled with the --spot-primary colour. It appears as a prominent button at the bottom of the modal.
{
"primary_cta": {
"text": "Upgrade Now",
"action": "url",
"url": "/billing/upgrade"
}
}Secondary CTA
A less prominent action button, styled as an outlined or text button.
{
"secondary_cta": {
"text": "Learn More",
"action": "url",
"url": "/docs/new-features"
}
}CTA Actions
| Action | Description |
|---|---|
dismiss | Close the modal |
url | Navigate to a URL |
track | Fire a custom event and close |
{
"primary_cta": {
"text": "Enable Dark Mode",
"action": "track",
"event_name": "dark_mode.enabled"
}
}Rich Content
Modal body content supports safe HTML:
{
"body": "<h3>Getting Started</h3><p>Follow these three steps:</p><ol><li>Connect your data source</li><li>Create your first dashboard</li><li>Invite your team</li></ol><p>Need help? <a href='https://docs.example.com' target='_blank'>Read the docs</a>.</p>"
}Supported HTML tags: b, i, strong, em, a, br, p, ul, ol, li, h3, h4, span, img, video.
WARNING
All on* event handler attributes, <script> tags, <iframe> tags, and javascript: URLs are stripped by the built-in sanitiser. Inline style attributes are restricted to a safe property allowlist (color, font-weight, font-size, text-align, text-decoration).
Media
Embed images or videos at the top of the modal:
{
"media": {
"type": "image",
"src": "https://cdn.example.com/feature-announcement.png",
"alt": "New dashboard layout showing the sidebar navigation"
}
}{
"media": {
"type": "video",
"src": "https://cdn.example.com/feature-demo.mp4",
"poster": "https://cdn.example.com/feature-poster.jpg",
"controls": true
}
}Backdrop
The backdrop overlay covers the entire viewport behind the modal. It is controlled by the --spot-overlay-bg theme variable.
When backdrop_click_dismisses is true (default), clicking anywhere on the backdrop closes the modal. Set to false for modals that require the user to take an explicit action (click a button or the close icon).
{
"backdrop_click_dismisses": false,
"show_close_button": true
}Dismissal
Users can dismiss a modal by:
- Clicking the dismiss button (e.g., "Close" or "Got It")
- Clicking the X close button (if
show_close_buttonistrue) - Clicking the backdrop (if
backdrop_click_dismissesistrue) - Pressing
Escape
Once dismissed, the modal's progress state is recorded. The modal will not reappear for that user unless demoMode is enabled.
Triggers
Common triggers for modals:
| Trigger | Description |
|---|---|
page_load | Show immediately on page load (default) |
first_visit | Show only on the user's first visit |
delay | Show after a delay (e.g., 3 seconds after page load) |
idle | Show after the user has been idle |
event | Show when a custom event fires |
manual | Surface the modal via custom code (call into the SDK's MCP server or its admin API). |
TIP
For announcements, use first_visit to ensure users see the modal once. For welcome modals, combine first_visit with a delay of 1-2 seconds to let the page render before showing the modal.
Programmatic Control
Modals fire on the trigger + audience rules attached in the admin panel. Hook the SDK's content:dismiss event when you want to react to a user closing one:
Spotlight.on('content:dismiss', (data) => {
console.log(`User dismissed content: ${data.contentId}`);
});For "fire on action X" flows, configure an event trigger in the dashboard and dispatch the matching event from your app.
Styling
Modals inherit all --spot-* theme variables. Key variables for modal appearance:
| Variable | Effect |
|---|---|
--spot-bg | Modal background colour |
--spot-text | Modal text colour |
--spot-border | Modal border colour |
--spot-radius-lg | Modal border radius |
--spot-shadow-lg | Modal box shadow |
--spot-overlay-bg | Backdrop overlay colour |
--spot-primary | Primary CTA button colour |
--spot-font-size-xl | Modal title font size |
Accessibility
Modals follow WAI-ARIA dialog best practices:
- The modal container has
role="dialog"andaria-modal="true" - Focus is trapped inside the modal while it is open
- Focus is returned to the previously focused element when the modal closes
- The modal title is referenced by
aria-labelledby - The Escape key closes the modal
- The backdrop is marked with
aria-hidden="true"