Skip to content

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

  1. Navigate to Content > Modals and click Create Modal.
  2. Write the modal title and body content.
  3. Add optional media (image, video).
  4. Configure action buttons (primary CTA, secondary action, dismiss).
  5. Set trigger, audience, and scheduling rules.
  6. Click Publish.

Via the API

All non-tour content types share the same admin endpoint — POST /v1/admin/content — with a content_type discriminator.

bash
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

FieldTypeRequiredDescription
namestringYesInternal name for the modal
titlestringYesModal heading text
bodystringYesModal body content (supports safe HTML)
mediaobjectNoEmbedded image or video
primary_ctaobjectNoPrimary call-to-action button
secondary_ctaobjectNoSecondary action button
dismiss_textstringNoDismiss button text (default: "Close")
sizestringNoModal width: small, medium, large (default: medium)
backdrop_click_dismissesbooleanNoWhether clicking the backdrop closes the modal (default: true)
show_close_buttonbooleanNoShow the X close button in the top-right corner (default: true)
triggerobjectNoWhen to show the modal
audienceobjectNoWho should see the modal

Sizes

SizeMax WidthUse Case
small400pxQuick announcements, confirmations
medium560pxFeature announcements, release notes (default)
large720pxDetailed content with media, onboarding flows
json
{ "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.

json
{
  "primary_cta": {
    "text": "Upgrade Now",
    "action": "url",
    "url": "/billing/upgrade"
  }
}

Secondary CTA

A less prominent action button, styled as an outlined or text button.

json
{
  "secondary_cta": {
    "text": "Learn More",
    "action": "url",
    "url": "/docs/new-features"
  }
}

CTA Actions

ActionDescription
dismissClose the modal
urlNavigate to a URL
trackFire a custom event and close
json
{
  "primary_cta": {
    "text": "Enable Dark Mode",
    "action": "track",
    "event_name": "dark_mode.enabled"
  }
}

Rich Content

Modal body content supports safe HTML:

json
{
  "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:

json
{
  "media": {
    "type": "image",
    "src": "https://cdn.example.com/feature-announcement.png",
    "alt": "New dashboard layout showing the sidebar navigation"
  }
}
json
{
  "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).

json
{
  "backdrop_click_dismisses": false,
  "show_close_button": true
}

Dismissal

Users can dismiss a modal by:

  1. Clicking the dismiss button (e.g., "Close" or "Got It")
  2. Clicking the X close button (if show_close_button is true)
  3. Clicking the backdrop (if backdrop_click_dismisses is true)
  4. 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:

TriggerDescription
page_loadShow immediately on page load (default)
first_visitShow only on the user's first visit
delayShow after a delay (e.g., 3 seconds after page load)
idleShow after the user has been idle
eventShow when a custom event fires
manualSurface 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:

js
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:

VariableEffect
--spot-bgModal background colour
--spot-textModal text colour
--spot-borderModal border colour
--spot-radius-lgModal border radius
--spot-shadow-lgModal box shadow
--spot-overlay-bgBackdrop overlay colour
--spot-primaryPrimary CTA button colour
--spot-font-size-xlModal title font size

Accessibility

Modals follow WAI-ARIA dialog best practices:

  • The modal container has role="dialog" and aria-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"

Spotlight