Host Environment Context
How to make your view adapt to the host’s theme, locale, display mode, and device.
Available Hooks
Section titled “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 |
Theme Adaptation with useLayout
Section titled “Theme Adaptation with useLayout”Match the host’s light/dark theme:
import { useLayout } from "@usefractal/frac/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
Section titled “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
Section titled “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
Section titled “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
Section titled “User Info with useUser”Access locale and device capabilities:
import { useUser } from "@usefractal/frac/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
Section titled “Available User Agent Properties”const { userAgent } = useUser();
userAgent.device.type; // "mobile" | "tablet" | "desktop"userAgent.capabilities.hover; // true if device has hover capabilityuserAgent.capabilities.touch; // true if device has touch capabilityDisplay Mode with useDisplayMode
Section titled “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 "@usefractal/frac/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
Section titled “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
Section titled “Modal Requests with useRequestModal”Modal Display Mode is not accessible via useDisplayMode, it can only be opened via useRequestModal.
import { useRequestModal } from "@usefractal/frac/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 similarExternal Links with useOpenExternal
Section titled “External Links with useOpenExternal”Open URLs outside the view:
import { useOpenExternal } from "@usefractal/frac/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> );}Responsive Design Pattern
Section titled “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> );}