Skip to main content
The useRequestModal hook returns a function to trigger a modal that is portaled outside of the widget iframe. This ensures the modal is correctly displayed and not limited to the widget’s area, allowing for a better user experience when displaying detailed content or forms.

Basic usage

import { useRequestModal } from "skybridge/web";

function ModalTrigger() {
  const { open } = useRequestModal();

  return (
    <button onClick={() => open({ title: "Details" })}>
      View Details
    </button>
  );
}

Returns

{
  isOpen: boolean;
  open: (options: RequestModalOptions) => void;
  params?: Record<string, unknown>;
}

Properties

  • isOpen: boolean - Whether the modal is currently open
  • open: (options: RequestModalOptions) => void - Function to trigger the modal
  • params?: Record<string, unknown> - Parameters passed when the modal was opened

RequestModalOptions

  • title?: string - The title to display in the modal header
  • params?: Record<string, unknown> - Custom parameters to pass to the modal
  • anchor?: { top?: number; left?: number; width?: number; height?: number } - Positioning anchor for the modal
When the modal is opened, your widget will be re-rendered and the isOpen property will be true. Use this to detect when you’re in modal mode and render different content accordingly.

Examples

import { useRequestModal } from "skybridge/web";

function ProductWidget() {
  const { open, isOpen } = useRequestModal();

  // When in modal mode, show detailed view
  if (isOpen) {
    return (
      <div className="product-details">
        <h2>Product Details</h2>
        <p>Full product description with all specifications...</p>
        <ul>
          <li>Feature 1</li>
          <li>Feature 2</li>
          <li>Feature 3</li>
        </ul>
        <button>Add to Cart</button>
      </div>
    );
  }

  // Inline view shows summary
  return (
    <div className="product-summary">
      <h3>Product Name</h3>
      <p>Brief description...</p>
      <button onClick={() => open({ title: "Product Details" })}>
        View Details
      </button>
    </div>
  );
}

Settings Modal

import { useRequestModal } from "skybridge/web";
import { useState } from "react";

function SettingsWidget() {
  const { open, isOpen } = useRequestModal();
  const [settings, setSettings] = useState({
    notifications: true,
    darkMode: false,
    language: "en",
  });

  if (isOpen) {
    return (
      <form className="settings-form">
        <label>
          <input
            type="checkbox"
            checked={settings.notifications}
            onChange={(e) =>
              setSettings((s) => ({ ...s, notifications: e.target.checked }))
            }
          />
          Enable Notifications
        </label>
        <label>
          <input
            type="checkbox"
            checked={settings.darkMode}
            onChange={(e) =>
              setSettings((s) => ({ ...s, darkMode: e.target.checked }))
            }
          />
          Dark Mode
        </label>
        <label>
          Language:
          <select
            value={settings.language}
            onChange={(e) =>
              setSettings((s) => ({ ...s, language: e.target.value }))
            }
          >
            <option value="en">English</option>
            <option value="fr">French</option>
            <option value="es">Spanish</option>
          </select>
        </label>
      </form>
    );
  }

  return (
    <div className="settings-summary">
      <span>Settings</span>
      <button onClick={() => open({ title: "Settings" })}>
        Configure
      </button>
    </div>
  );
}
import { useRequestModal } from "skybridge/web";
import { useState } from "react";

type Image = {
  id: string;
  thumbnail: string;
  full: string;
  alt: string;
};

function ImageGallery({ images }: { images: Image[] }) {
  const { open, isOpen } = useRequestModal();
  const [selectedIndex, setSelectedIndex] = useState(0);

  if (isOpen) {
    const current = images[selectedIndex];
    return (
      <div className="gallery-modal">
        <img src={current.full} alt={current.alt} />
        <div className="gallery-nav">
          <button
            onClick={() => setSelectedIndex((i) => Math.max(0, i - 1))}
            disabled={selectedIndex === 0}
          >
            Previous
          </button>
          <span>
            {selectedIndex + 1} / {images.length}
          </span>
          <button
            onClick={() =>
              setSelectedIndex((i) => Math.min(images.length - 1, i + 1))
            }
            disabled={selectedIndex === images.length - 1}
          >
            Next
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="gallery-thumbnails">
      {images.slice(0, 4).map((image, index) => (
        <button
          key={image.id}
          onClick={() => {
            setSelectedIndex(index);
            open({ title: "Gallery" });
          }}
        >
          <img src={image.thumbnail} alt={image.alt} />
        </button>
      ))}
      {images.length > 4 && <span>+{images.length - 4} more</span>}
    </div>
  );
}

Form Modal

import { useRequestModal, useWidgetState } from "skybridge/web";

type FormData = {
  name: string;
  email: string;
  submitted: boolean;
};

function ContactForm() {
  const { open, isOpen } = useRequestModal();
  const [formData, setFormData] = useWidgetState<FormData>({
    name: "",
    email: "",
    submitted: false,
  });

  if (isOpen) {
    if (formData.submitted) {
      return (
        <div className="form-success">
          <h2>Thank you, {formData.name}!</h2>
          <p>We'll be in touch at {formData.email}</p>
        </div>
      );
    }

    return (
      <form
        className="contact-form"
        onSubmit={(e) => {
          e.preventDefault();
          setFormData((prev) => ({ ...prev, submitted: true }));
        }}
      >
        <input
          type="text"
          placeholder="Your name"
          value={formData.name}
          onChange={(e) =>
            setFormData((prev) => ({ ...prev, name: e.target.value }))
          }
          required
        />
        <input
          type="email"
          placeholder="Your email"
          value={formData.email}
          onChange={(e) =>
            setFormData((prev) => ({ ...prev, email: e.target.value }))
          }
          required
        />
        <button type="submit">Submit</button>
      </form>
    );
  }

  return (
    <button onClick={() => open({ title: "Contact Us" })}>
      Contact Us
    </button>
  );
}