# PopoverPanel **📖 Live documentation:** https://cds.coinbase.com/components/overlay/PopoverPanel/ PopoverPanel anchors an elevated floating panel to a trigger element. ## Import ```tsx import { PopoverPanel } from '@coinbase/cds-web/popover' ``` ## Examples ### Basics Pass `content` for the panel body and `children` as the trigger. The trigger toggles open and closed on press; the panel applies focus management and escape-to-close behavior. ```jsx live function BasicExample() { return ( ( Panel title Arbitrary content for a floating panel. )} accessibilityLabel="Example settings panel" > ); } ``` ### Selectable list Use [`ListCell`](/components/data-display/ListCell/) with local state for the selected row and `closePopover` from the `content` render callback. You do not need `SelectProvider` or `SelectContext`. After a value is chosen, the trigger often shows only the title. Set **`accessibilityLabel`** on the trigger to include the same details a sighted user gets from the list (for example title and description). Optionally set the panel **`accessibilityLabel`** so the dialog name matches the task (first choice vs. changing the value). ```jsx live function ListCellSelectExample() { const [selectedId, setSelectedId] = useState(null); const options = [ { id: 'eth', title: 'Ethereum', description: 'Main network' }, { id: 'base', title: 'Base', description: 'L2 network' }, { id: 'sol', title: 'Solana', description: 'External wallet' }, ]; const selected = options.find((o) => o.id === selectedId); return ( ( {options.map((option) => ( { setSelectedId(option.id); closePopover(); }} /> ))} )} > ); } ``` ### Overlay and placement Use `showOverlay` to dim content behind the panel. Adjust floating placement with `contentPosition` (see [Floating UI placement](https://floating-ui.com/docs/useFloating#placement)). ```jsx live function OverlayAndPlacementExample() { return ( ( Content with overlay and top placement. )} showOverlay accessibilityLabel="Panel with overlay" > ( Content with overlay and top placement. )} contentPosition={{ placement: 'top', gap: 1 }} accessibilityLabel="Panel above trigger" > ); } ``` ### Panel sizing By default, the panel content uses the same width as the trigger. Set `panelWidth`, `minPanelWidth`, `maxPanelWidth`, and `maxPanelHeight` when you need different constraints. The default max height is exported as `POPOVER_PANEL_MAX_HEIGHT`. ```jsx live function SizingExample() { return ( ( {Array.from({ length: 12 }, (_, i) => ( Row {i + 1} ))} )} panelWidth={280} maxPanelHeight={200} accessibilityLabel="Scrollable panel" > ); } ``` ### Mobile modal On small viewports, pass `enableMobileModal` to render the panel in a modal shell instead of a floating popover. ```jsx live function MobileModalExample() { return ( ( Modal-style panel Useful when the floating surface would be cramped on phone breakpoints. )} enableMobileModal accessibilityLabel="Settings in modal" panelWidth={320} maxPanelWidth="80vw" > ); } ``` ### Imperative open and close Use a ref to call `openPopover` and `closePopover` when you need to drive visibility from elsewhere (for example, a separate control or analytics callback). ```jsx live function ImperativeExample() { const panelRef = useRef(null); return ( Panel opened from an external button. } accessibilityLabel="Programmatic panel" > ); } ``` ## Props | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `children` | `null \| string \| number \| false \| true \| ReactElement> \| Iterable \| ReactPortal` | Yes | `-` | Subject of the Popover that when interacted with will toggle the visibility of the content | | `content` | `ReactNode \| PopoverPanelRenderContent` | Yes | `-` | Panel body, or a function that receives closePopover (helpfulwhen actions inside the panel should dismiss it). | | `block` | `boolean` | No | `-` | Makes the Popover Subject fill the width of the parent container | | `className` | `string` | No | `-` | - | | `classNames` | `{ content?: string; triggerContainer?: string \| undefined; } \| undefined` | No | `-` | - | | `contentPosition` | `PopoverContentPositionConfig` | No | `-` | Override content positioning defaults | | `controlledElementAccessibilityProps` | `{ id: string; accessibilityLabel?: string; } \| undefined` | No | `-` | - | | `disablePortal` | `boolean` | No | `-` | Does not render the panel inside of a portal (react-dom createPortal). Portal is automatically disabled for SSR | | `disableTypeFocus` | `boolean` | No | `-` | Use for editable Search Input components to ensure focus is correctly applied | | `disabled` | `boolean` | No | `-` | Prevents the panel from opening. Youll need to surface disabled state on the trigger manually. | | `enableMobileModal` | `boolean` | No | `-` | Enable to have PopoverPanel render its content inside a Modal as opposed to a relatively positioned Popover. Ideal for mobile or smaller devices. | | `key` | `Key \| null` | No | `-` | - | | `maxPanelHeight` | `ResponsiveProp>` | No | `300` | Can optionally pass a maxHeight. | | `maxPanelWidth` | `ResponsiveProp>` | No | `-` | Maximum width of the panel as a percentage string or number converted to pixels. | | `minPanelWidth` | `ResponsiveProp>` | No | `-` | Minimum width of the panel as a percentage string or number converted to pixels. | | `onBlur` | `(() => void)` | No | `-` | Callback that fires when PopoverPanel or trigger are blurred | | `onClose` | `(() => void)` | No | `-` | Callback that fires when PopoverPanel is closed | | `onOpen` | `(() => void)` | No | `-` | Callback that fires when PopoverPanel is opened | | `panelHeight` | `ResponsiveProp>` | No | `-` | Height of the panel as a percentage string or number converted to pixels. | | `panelWidth` | `ResponsiveProp>` | No | `-` | Width of the panel as a percentage string or number converted to pixels. | | `ref` | `null \| string \| RefObject \| (instance: HTMLDivElement \| null) => void` | No | `-` | Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref). | | `respectNegativeTabIndex` | `boolean` | No | `-` | If true, the focus trap will respect negative tabIndex values, removing them from the list of focusable elements. | | `restoreFocusOnUnmount` | `boolean` | No | `true` | If true, the focus trap will restore focus to the previously focused element when it unmounts. WARNING: If you disable this, you need to ensure that focus is restored properly so it doesnt end up on the body | | `showOverlay` | `boolean` | No | `-` | Display an overlay over all content below the Popover menu | | `style` | `CSSProperties` | No | `-` | - | | `styles` | `{ content?: CSSProperties; triggerContainer?: CSSProperties \| undefined; } \| undefined` | No | `-` | - | | `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID | ## Styles | Selector | Static class name | Description | | --- | --- | --- | | `content` | `-` | elevated panel surface (PopoverPanelContent). | | `triggerContainer` | `-` | wrapper around children (the Popover root in floating layout, or the trigger Box in the mobile modal). |