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
Modal with Different Content
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>
);
}
Image Gallery Modal
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>
);
}
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>
);
}