Documentation Index
Fetch the complete documentation index at: https://docs-staging.skybridge.tech/llms.txt
Use this file to discover all available pages before exploring further.
How to make your view adapt to the host’s theme, locale, display mode, and device.
Available Hooks
| Hook | What it provides |
|---|
useLayout | Theme, safe areas, max height |
useUser | Locale, user agent, device capabilities |
useDisplayMode | Current display mode, mode switching |
useRequestModal | Open view in modal |
useOpenExternal | Open external URLs |
MCP Apps Runtime: useRequestModal is polyfilled in MCP Apps. Modals render inside the view iframe rather than being portaled to the host. See useRequestModal API for details.
Theme Adaptation with useLayout
Match the host’s light/dark theme:
import { useLayout } from "skybridge/web";
function ThemedView() {
const { theme } = useLayout();
const isDark = theme === "dark";
return (
<div style={{
backgroundColor: isDark ? "#1a1a1a" : "#ffffff",
color: isDark ? "#ffffff" : "#000000",
padding: "16px",
}}>
<h1>Hello</h1>
</div>
);
}
With CSS Variables
function ThemedView() {
const { theme } = useLayout();
return (
<div
className="view"
data-theme={theme}
style={{
"--bg": theme === "dark" ? "#1a1a1a" : "#ffffff",
"--text": theme === "dark" ? "#ffffff" : "#000000",
} as React.CSSProperties}
>
{/* Content */}
</div>
);
}
.view {
background: var(--bg);
color: var(--text);
}
Safe Areas
Handle device notches and navigation bars:
const { safeArea } = useLayout();
<div style={{
paddingTop: safeArea.top,
paddingBottom: safeArea.bottom,
paddingLeft: safeArea.left,
paddingRight: safeArea.right,
}}>
{/* Content */}
</div>
Max Height
Respect the container’s max height:
const { maxHeight } = useLayout();
<div style={{
maxHeight: maxHeight ?? "100vh",
overflow: "auto",
}}>
{/* Scrollable content */}
</div>
User Info with useUser
Access locale and device capabilities:
Locale provided by OpenAIThe locale value comes from OpenAI’s host environment, not directly from the user’s browser. It may not always match the user’s actual browser locale settings.
import { useUser } from "skybridge/web";
function LocalizedView() {
const { locale, userAgent } = useUser();
// Format currency based on locale
const formatPrice = (amount: number) => {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD",
}).format(amount);
};
// Check device capabilities
const isMobile = userAgent.device.type === "mobile";
const hasHover = userAgent.capabilities.hover;
return (
<div>
<p>Price: {formatPrice(99.99)}</p>
{isMobile ? (
<button className="large-tap-target">Buy Now</button>
) : (
<button className={hasHover ? "hoverable" : ""}>Buy Now</button>
)}
</div>
);
}
Available User Agent Properties
const { userAgent } = useUser();
userAgent.device.type; // "mobile" | "tablet" | "desktop"
userAgent.capabilities.hover; // true if device has hover capability
userAgent.capabilities.touch; // true if device has touch capability
Display Mode with useDisplayMode
Views can render in different display modes:
| Mode | Description |
|---|
pip | Picture-in-picture (small floating window) |
inline | Inline with the conversation |
fullscreen | Full screen takeover |
modal | Modal overlay |
import { useDisplayMode } from "skybridge/web";
function AdaptiveView() {
const { displayMode, setDisplayMode } = useDisplayMode();
// Render differently based on mode
if (displayMode === "pip") {
return <CompactView />;
}
return (
<div>
<FullView />
<button onClick={() => setDisplayMode("fullscreen")}>
Go Fullscreen
</button>
</div>
);
}
Requesting Mode Change
const { setDisplayMode } = useDisplayMode();
// Go fullscreen for immersive content
<button onClick={() => setDisplayMode("fullscreen")}>
View Full Map
</button>
// Back to inline
<button onClick={() => setDisplayMode("inline")}>
Close Map
</button>
Modal Requests with useRequestModal
Modal Display Mode is not accessible via useDisplayMode, it can only be opened via useRequestModal.
import { useRequestModal } from "skybridge/web";
function ProductCard({ product }) {
const { open } = useRequestModal();
const showDetails = () => {
open({
title: product.name,
params: { productId: product.id },
});
};
return (
<div onClick={showDetails}>
<img src={product.image} alt={product.name} />
<p>{product.name}</p>
</div>
);
}
// In your modal component, access params via useToolInfo or similar
MCP Apps Runtime: useRequestModal is polyfilled in MCP Apps. Modals render inside the view iframe rather than being portaled to the host. See useRequestModal API for details.
External Links with useOpenExternal
Open URLs outside the view:
import { useOpenExternal } from "skybridge/web";
function LinkView() {
const openExternal = useOpenExternal();
const openDocs = () => {
openExternal("https://docs.example.com");
};
const openApp = () => {
// Open mobile app if available, do not include ChatGPT redirect URL
openExternal("myapp://deep-link", { redirectUrl: false });
};
return (
<div>
<button onClick={openDocs}>View Documentation</button>
<button onClick={openApp}>Open in App</button>
</div>
);
}
External links open in a new tab/window. Some URLs may be blocked by the iframe’s CSP policy.
Responsive Design Pattern
Combine hooks for a fully adaptive view:
function ResponsiveView() {
const { theme, safeArea, maxHeight } = useLayout();
const { locale, userAgent } = useUser();
const { displayMode, setDisplayMode } = useDisplayMode();
const isMobile = userAgent.device.type === "mobile";
const isDark = theme === "dark";
return (
<div
style={{
backgroundColor: isDark ? "#1a1a1a" : "#ffffff",
color: isDark ? "#ffffff" : "#000000",
paddingTop: safeArea.top,
paddingBottom: safeArea.bottom,
maxHeight: maxHeight ?? "100vh",
overflow: "auto",
}}
>
{displayMode === "pip" ? (
<MiniView onExpand={() => setDisplayMode("inline")} />
) : (
<FullView
isMobile={isMobile}
locale={locale}
onMinimize={() => setDisplayMode("pip")}
/>
)}
</div>
);
}