Widget display modesPro
One shell that moves a widget between inline, inline carousel, full screen and picture in picture without remounting it, with spatially honest transitions, host safe areas, max height and the current mode in context.
On this page
Installation
Usage
import { WidgetDisplayModes } from "@/components/ui/chatcn/pro/widget-display-modes"
import { useWidgetDisplayMode } from "@/hooks/use-chatcn-pro-host"
function PlacesWidget() {
const mode = useWidgetDisplayMode()
return mode === "carousel" ? <PlaceCards /> : <PlaceList />
}
<WidgetDisplayModes
title="Friday in Lisbon"
modes={["inline", "carousel", "fullscreen", "pip"]}
composer={<PromptInput />}
>
<PlacesWidget />
</WidgetDisplayModes>The widget is one element restyled between modes, never moved or remounted, so React state, media and iframes survive every change. While it is out of the conversation, a placeholder keeps the slot's height so the answer below never jumps, and offers Show it here.
Widgets read their host through useWidgetHost() from use-chatcn-pro-host: displayMode, requestDisplayMode, maxHeight, safeArea, theme, locale, dir, toolInput, toolOutput, widgetState and the calls callTool, sendFollowUp, setWidgetState and openExternal. The contract mirrors the Apps SDK window.openai globals, so a widget built against it ports to ChatGPT Apps or MCP Apps by swapping the provider.
Use strategy="absolute" to keep full screen and PiP inside a positioned container (a chat pane, a device frame) instead of the viewport. On small touch screens a request for PiP becomes full screen, like real hosts do; turn that off with coercePip={false}.
Examples
Inline carousel
In carousel mode the shell drops its card so your items can be cards. WidgetCarousel and WidgetCarouselItem give you a scroll snapping track with arrow keys and previous and next buttons on pointer devices.
Max height, empty and RTL
Props
| Prop | Type | Notes |
|---|---|---|
mode, defaultMode, onModeChange | "inline" | "carousel" | "fullscreen" | "pip" | Controlled or uncontrolled |
modes | WidgetDisplayMode[] | What the host offers. Default inline, full screen and PiP |
title, subtitle, icon | ReactNode | Header. The icon is your app mark |
actions | ReactNode | Header buttons before the mode buttons |
composer | ReactNode | Docked under the widget in full screen |
maxHeight | number | Inline limit in px. Default 480 |
contentWidth | number | "full" | Full screen content width. Default 768 |
safeArea | Partial<WidgetSafeArea> | Insets to keep clear, such as { bottom: 72 } for a composer |
strategy | "fixed" | "absolute" | Viewport or nearest positioned parent. Default fixed |
pipCorner, defaultPipCorner, onPipCornerChange | PipCorner | Logical corners: bottom-end is bottom right in LTR |
pipWidth | number | Default 360 |
summary | ReactNode | Live text in the minimized PiP bar |
coercePip | boolean | PiP becomes full screen on small touch screens. Default true |
Keyboard
| Key | Action |
|---|---|
Tab | Moves through the header buttons and the widget. Stays inside in full screen |
Escape | Leaves full screen or PiP and returns focus to the button for that mode |
ArrowLeft, ArrowRight | Move card by card in the carousel |
| Arrow keys on the grip | Move the PiP window to another corner |
Accessibility
- Inline and PiP are named regions; full screen is a modal dialog labelled by the title, with focus moved in and trapped.
- When a mode change removes the button that triggered it, focus lands on the PiP grip or back on the matching mode button, never on the page body.
- The placeholder says where the widget went and offers a button to bring it back.
Motion
- Inline to full screen: the frame is laid out full size, translated onto the card and clipped to it, then both run to zero in 280 ms. Nothing stretches; the header slides from the slot to the top.
- Inline and PiP: the window flies between the slot and its corner on transform only, 240 ms, with a uniform scale so text never distorts.
- Full screen to inline fades the card in from 0.98 in 160 ms. Inline and carousel crossfade in 150 ms.
- Changes made from the keyboard are instant. Reduced motion turns every transition into a 150 ms fade.