Files
zeekayandHanzo Dev 751ef5c778 rename the framework to @hanzo/gui and the loader to @hanzogui/loader
Both old names are owned by a different npm account (artemis-prime), so
publishing them returns 403 — which is how this surfaced: the 8.0.0 run
shipped @hanzogui/logo and @hanzogui/demos depending on [email protected], a
version that cannot be published and therefore does not exist. Those two
were uninstallable.

@hanzo/gui and @hanzogui/* are ours, so the framework now carries the name
it is imported by everywhere downstream, and the loader moves under the
scope we control.

The rewrite is limited to package references. `DEBUG?.startsWith('hanzogui')`
is a log-namespace filter and join(dir, 'hanzogui') is a path — rewriting
either would change behaviour, so both are untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-30 20:23:18 -07:00

348 lines
9.6 KiB
Plaintext

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---
<HeroContainer showAnimationDriverControl>
<PopoverDemo />
</HeroContainer>
```tsx hero template=Popover
```
<Highlights
features={[
`Optional arrow to point to content.`,
`Keeps within bounds of page.`,
`Can be placed into 12 anchor positions.`,
]}
/>
Popovers are a great way to show content that's only visible when trigger is
pressed, floating above the current content.
<Notice>
Note: Popovers are not a recommended pattern for mobile apps, and so aren't supported on
native. Instead you can use Adapt and render them as a Sheet, or just conditionally
render them. We landed support for them at one point, but we need the community to
contribute tests in order to support them for mobile again.
</Notice>
## Installation
Popover is already installed in `hanzogui`, or you can install it independently:
```bash
npm install @hanzogui/popover
```
### PortalProvider
When rendering into root of app instead of inline, you'll first need to install
the `@hanzogui/portal` package:
```bash
npm install @hanzogui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@hanzogui/portal'
import YourApp from './components/YourApp'
function App() {
return (
<PortalProvider shouldAddRootHost>
<YourApp />
</PortalProvider>
)
}
export default App
```
<PropsTable
data={[
{
name: 'shouldAddRootHost',
type: 'boolean',
required: false,
description: `Defines whether to add a default root host or not.`,
},
]}
/>
## Anatomy
```tsx
import { Popover, Adapt } from '@hanzo/gui' // or '@hanzogui/popover'
export default () => (
<Popover>
<Popover.Trigger />
{/* Optional: Control focus behavior */}
<Popover.FocusScope loop trapped focusOnIdle={true}>
<Popover.Content>
<Popover.Arrow />
<Popover.Close />
{/* ScrollView is optional, can just put any contents inside if not scrollable */}
<Popover.ScrollView>{/* ... */}</Popover.ScrollView>
{/* ... */}
</Popover.Content>
</Popover.FocusScope>
{/* optionally change to sheet when small screen */}
{/* you can also use <Popover.Adapt /> */}
<Adapt when="maxMd">
<Popover.Sheet>
<Popover.Sheet.Overlay />
<Popover.Sheet.Frame>
<Popover.Sheet.ScrollView>
<Adapt.Contents />
</Popover.Sheet.ScrollView>
</Popover.Sheet.Frame>
</Popover.Sheet>
</Adapt>
</Popover>
)
```
## API Reference
### Popover
Contains every component for the popover.
<PropsTable
data={[
{
name: 'children',
type: 'React.ReactNode',
required: true,
description: `Must contain Popover.Content`,
},
{
name: 'size',
type: 'SizeTokens',
required: false,
description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
},
{
name: 'placement',
type: 'Placement',
required: false,
description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
},
{
name: 'open',
type: 'boolean',
required: false,
description: ``,
},
{
name: 'defaultOpen',
type: 'boolean',
required: false,
},
{
name: 'onOpenChange',
type: '(open: boolean) => void',
required: false,
},
{
name: 'keepChildrenMounted',
type: 'boolean | "lazy"',
required: false,
description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.`,
},
{
name: 'stayInFrame',
type: 'ShiftProps | boolean',
required: false,
description: `Keeps the Popover inside the frame, see floating-ui shift().`,
},
{
name: 'allowFlip',
type: 'FlipProps | boolean',
required: false,
description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
},
{
name: 'hoverable',
type: 'boolean | UseFloatingProps',
required: false,
description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
},
{
name: 'resize',
type: 'SizeProps | boolean',
required: false,
description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
},
]}
/>
For most of these properties, you'll want to reference the
[floating-ui docs](https://floating-ui.com/docs/getting-started).
<Notice>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](/ui/popover/1.83.0#portalprovider) for more information.
</Notice>
### Popover.Arrow
Popover.Arrow can be used to show an arrow that points at the Trigger element.
In order for the Arrow to show you must have a Trigger element within your
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).
### Popover.Trigger
Used to trigger opening of the popover when uncontrolled, just renders a YStack,
see [Stacks](/docs/components/stacks).
### Popover.Content
Extends PopperContent which extends SizableStack which extends a YStack (see
[Stacks](/docs/components/stacks)).
Also extends
Used to display the content of the popover.
<PropsTable
data={[
{
name: 'enableAnimationForPositionChange',
type: 'boolean',
description:
'Disabled animate presence animations in favor of regular animation, useful for doing sliding popovers.',
},
{
name: 'size',
type: 'SizeTokens',
required: false,
description: 'Controls default padding/borderRadius when unstyled is false.',
},
{
name: 'unstyled',
required: false,
type: `boolean`,
default: false,
description: `Removes all default Gui styles.`,
},
{
name: 'trapFocus',
type: 'boolean',
default: false,
description: 'Whether focus should be trapped within the `Popover`',
},
{
name: 'disableFocusScope',
type: 'boolean',
default: false,
description: 'Whether popover should not focus contents on open',
},
{
name: 'onOpenAutoFocus',
type: `FocusScopeProps['onMountAutoFocus']`,
default: false,
description: 'Event handler called when auto-focusing on open. Can be prevented.',
},
{
name: 'onCloseAutoFocus',
type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
default: false,
description: 'Event handler called when auto-focusing on close. Can be prevented.',
},
{
name: 'lazyMount',
required: false,
type: `boolean`,
description: `Removes all default Gui styles.`,
},
]}
/>
### Popover.Anchor
Renders as YStack, see [Stacks](/docs/components/stacks).
When you want the Trigger to be in another location from where the Popover
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
Trigger will open it.
### Popover.Sheet
When used with `Adapt`, Popover will render as a sheet when that breakpoint is
active.
See [Sheet](/docs/components/sheet) for more props.
Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the
contents given to `Popover.Content`
### Popover.FocusScope
Provides access to the underlying FocusScope component used by Popover for focus
management. Can be used to control focus behavior from a parent component.
<PropsTable
data={[
{
name: 'enabled',
type: 'boolean',
default: 'true',
description: `Whether focus management is enabled`,
},
{
name: 'loop',
type: 'boolean',
default: 'false',
description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
},
{
name: 'trapped',
type: 'boolean',
default: 'false',
description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
},
{
name: 'focusOnIdle',
type: 'boolean | number',
default: 'false',
description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
},
{
name: 'onMountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Popover.ScrollView
Must be nested inside Content. Renders as a plain React Native ScrollView. If
used alongside `<Adapt />` and Sheet, Gui will automatically know to remove
this ScrollView when swapping into the Sheet, as the Sheet must use it's own
ScrollView that handles special logic for interactions with dragging.