> ## Documentation Index
> Fetch the complete documentation index at: https://docs.skybridge.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Host Environment Context

> Adapt your view to the host's theme, locale, display mode, and device with environment hooks.

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

## Available Hooks

| Hook                                                  | What it provides                        |
| ----------------------------------------------------- | --------------------------------------- |
| [`useLayout`](/api-reference/use-layout)              | Theme, safe areas, max height           |
| [`useUser`](/api-reference/use-user)                  | Locale, user agent, device capabilities |
| [`useDisplayMode`](/api-reference/use-display-mode)   | Current display mode, mode switching    |
| [`useRequestModal`](/api-reference/use-request-modal) | Open view in modal                      |
| [`useOpenExternal`](/api-reference/use-open-external) | Open external URLs                      |

<Warning>
  **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](/api-reference/use-request-modal) for details.
</Warning>

## Theme Adaptation with useLayout

Match the host's light/dark theme:

```tsx theme={null}
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

```tsx theme={null}
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>
  );
}
```

```css theme={null}
.view {
  background: var(--bg);
  color: var(--text);
}
```

### Safe Areas

Handle device notches and navigation bars:

```tsx theme={null}
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:

```tsx theme={null}
const { maxHeight } = useLayout();

<div style={{
  maxHeight: maxHeight ?? "100vh",
  overflow: "auto",
}}>
  {/* Scrollable content */}
</div>
```

## User Info with useUser

Access locale and device capabilities:

<Note>
  **Locale provided by OpenAI**

  The 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.
</Note>

```tsx theme={null}
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

```typescript theme={null}
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                              |

```tsx theme={null}
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

```tsx theme={null}
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.

```tsx theme={null}
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
```

<Warning>
  **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](/api-reference/use-request-modal) for details.
</Warning>

## External Links with useOpenExternal

Open URLs outside the view:

```tsx theme={null}
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>
  );
}
```

<Note>
  External links open in a new tab/window. Some URLs may be blocked by the iframe's CSP policy.
</Note>

## Responsive Design Pattern

Combine hooks for a fully adaptive view:

```tsx theme={null}
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>
  );
}
```

## Related

* [useLayout API](/api-reference/use-layout)
* [useUser API](/api-reference/use-user)
* [useDisplayMode API](/api-reference/use-display-mode)
* [useRequestModal API](/api-reference/use-request-modal)
* [useOpenExternal API](/api-reference/use-open-external)
