Skip to content
Sign in
Apps SDKWidget display modesMenu

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.

ProAn evening plan in Lisbon that moves between inline, cards, full screen and picture in picture without remounting.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/widget-display-modes

Usage

tsx
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

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.

ProThe same widget as a row of snapping cards, with arrow keys and previous and next buttons.

Max height, empty and RTL

ProLong content scrolling inside the max height and an empty result.

Props

PropTypeNotes
mode, defaultMode, onModeChange"inline" | "carousel" | "fullscreen" | "pip"Controlled or uncontrolled
modesWidgetDisplayMode[]What the host offers. Default inline, full screen and PiP
title, subtitle, iconReactNodeHeader. The icon is your app mark
actionsReactNodeHeader buttons before the mode buttons
composerReactNodeDocked under the widget in full screen
maxHeightnumberInline limit in px. Default 480
contentWidthnumber | "full"Full screen content width. Default 768
safeAreaPartial<WidgetSafeArea>Insets to keep clear, such as { bottom: 72 } for a composer
strategy"fixed" | "absolute"Viewport or nearest positioned parent. Default fixed
pipCorner, defaultPipCorner, onPipCornerChangePipCornerLogical corners: bottom-end is bottom right in LTR
pipWidthnumberDefault 360
summaryReactNodeLive text in the minimized PiP bar
coercePipbooleanPiP becomes full screen on small touch screens. Default true

Keyboard

KeyAction
TabMoves through the header buttons and the widget. Stays inside in full screen
EscapeLeaves full screen or PiP and returns focus to the button for that mode
ArrowLeft, ArrowRightMove card by card in the carousel
Arrow keys on the gripMove 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.