Skip to content

Host Environment Context

How to make your view adapt to the host’s theme, locale, display mode, and device.

HookWhat it provides
useLayoutTheme, safe areas, max height
useUserLocale, user agent, device capabilities
useDisplayModeCurrent display mode, mode switching
useRequestModalOpen view in modal
useOpenExternalOpen external URLs

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>
);
}
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);
}

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>

Respect the container’s max height:

const { maxHeight } = useLayout();
<div style={{
maxHeight: maxHeight ?? "100vh",
overflow: "auto",
}}>
{/* Scrollable content */}
</div>

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>
);
}
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

Views can render in different display modes:

ModeDescription
pipPicture-in-picture (small floating window)
inlineInline with the conversation
fullscreenFull screen takeover
modalModal 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>
);
}
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 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 similar

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>
);
}

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>
);
}