Skip to Content
ComponentsDropdown

Dropdown

The Dropdown component displays content in a floating panel triggered by user interaction. Based on a modified version of Tamagui’s Popover.

Features

  • Optional arrow pointing to content
  • Keeps within page bounds
  • Can be placed in 12 anchor positions
  • Mobile adaptation support via Sheet
  • Focus and scroll management
  • Hover interaction support
  • Scoping system for performance optimization

Anatomy

Import all parts and piece them together.

import { Dropdown, Adapt } from '@xsolla-zk/react' export default () => ( <Dropdown> <Dropdown.Trigger /> <Dropdown.FocusScope> <Dropdown.Content> <Dropdown.Close /> <Dropdown.ScrollView> {/* content */} </Dropdown.ScrollView> </Dropdown.Content> </Dropdown.FocusScope> <Adapt when="maxMd"> <Dropdown.Sheet> <Dropdown.Sheet.Overlay /> <Dropdown.Sheet.Frame> <Dropdown.Sheet.ScrollView> <Adapt.Contents /> </Dropdown.Sheet.ScrollView> </Dropdown.Sheet.Frame> </Dropdown.Sheet> </Adapt> </Dropdown> )

Examples

Basic example

import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' function App() { return ( <Dropdown> <Dropdown.Trigger asChild> <Button> <Button.Text>Open menu</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Stack padding="$400"> <Typography>Dropdown content</Typography> <Dropdown.Close asChild> <Button> <Button.Text>Close</Button.Text> </Button> </Dropdown.Close> </Stack> </Dropdown.Content> </Dropdown> ) }

With focus management

import { Dropdown, Button, Stack } from '@xsolla-zk/react' function App() { return ( <Dropdown> <Dropdown.Trigger asChild> <Button> <Button.Text>Focus menu</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.FocusScope loop trapped focusOnIdle={true}> <Dropdown.Content> <Stack padding="$400" gap="$200"> <Button> <Button.Text>First button</Button.Text> </Button> <Button> <Button.Text>Second button</Button.Text> </Button> <Dropdown.Close asChild> <Button> <Button.Text>Close</Button.Text> </Button> </Dropdown.Close> </Stack> </Dropdown.Content> </Dropdown.FocusScope> </Dropdown> ) }

With mobile adaptation

import { Dropdown, Adapt, Button, Stack, Typography } from '@xsolla-zk/react' function App() { return ( <Dropdown> <Dropdown.Trigger asChild> <Button> <Button.Text>Adaptive menu</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Stack padding="$400"> <Typography>Content for desktop</Typography> </Stack> </Dropdown.Content> <Adapt when="maxMd"> <Dropdown.Sheet> <Dropdown.Sheet.Overlay /> <Dropdown.Sheet.Frame> <Dropdown.Sheet.ScrollView> <Adapt.Contents /> </Dropdown.Sheet.ScrollView> </Dropdown.Sheet.Frame> </Dropdown.Sheet> </Adapt> </Dropdown> ) }

With hover interaction

import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' function App() { return ( <Dropdown hoverable> <Dropdown.Trigger asChild> <Button> <Button.Text>Hover me</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Stack padding="$400"> <Typography>Content appears on hover</Typography> </Stack> </Dropdown.Content> </Dropdown> ) }

Controlled state

import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' import { useState } from 'react' function App() { const [open, setOpen] = useState(false) return ( <Dropdown open={open} onOpenChange={setOpen}> <Dropdown.Trigger asChild> <Button> <Button.Text>Controlled menu</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Stack padding="$400" gap="$200"> <Typography>State: {open ? 'open' : 'closed'}</Typography> <Button onPress={() => setOpen(false)}> <Button.Text>Close</Button.Text> </Button> </Stack> </Dropdown.Content> </Dropdown> ) }

With scrolling

import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' function App() { return ( <Dropdown> <Dropdown.Trigger asChild> <Button> <Button.Text>Long list</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Dropdown.ScrollView maxHeight="$200"> {Array.from({ length: 20 }, (_, i) => ( <Stack key={i} padding="$200"> <Typography>Item {i + 1}</Typography> </Stack> ))} </Dropdown.ScrollView> </Dropdown.Content> </Dropdown> ) }

With custom anchor

import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' function App() { return ( <Dropdown> <Dropdown.Anchor> <Stack> <Typography>Anchor here</Typography> </Stack> </Dropdown.Anchor> <Dropdown.Trigger asChild> <Button> <Button.Text>Trigger elsewhere</Button.Text> </Button> </Dropdown.Trigger> <Dropdown.Content> <Stack padding="$400"> <Typography>Attached to anchor, not trigger</Typography> </Stack> </Dropdown.Content> </Dropdown> ) }

Scoping

Dropdown supports scoping which allows you to mount one or more Dropdown instances at the root of your app, while having a deeply nested child Trigger or Content attach to the proper parent Dropdown instance.

In performance sensitive areas you may want to take advantage of this as it allows you to only render the Dropdown.Trigger inside the sensitive area.

// layout.tsx import { Dropdown, Button, Stack, Typography } from '@xsolla-zk/react' export default ({ children }) => ( <Dropdown scope="user-menu"> <Dropdown.Content> <Stack padding="$400" gap="$200"> <Typography>User menu</Typography> <Dropdown.Close asChild> <Button> <Button.Text>Close</Button.Text> </Button> </Dropdown.Close> </Stack> </Dropdown.Content> {children} </Dropdown> ) // UserButton.tsx export default () => ( <Dropdown.Trigger scope="user-menu" asChild> <Button> <Button.Text>User</Button.Text> </Button> </Dropdown.Trigger> )

API

Dropdown extends Tamagui stack views, inheriting all standard props, plus:

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseInitial state for uncontrolled usage
onOpenChange(open: boolean, via?: DropdownVia) => void-Called when state changes
keepChildrenMountedboolean | 'lazy'falseKeep children in DOM when closed
hoverableboolean | UseHoverPropsfalseAllow opening on hover
disableFocusbooleanfalseDisable focus behavior on open
sizeDropdownSizes'$500'Dropdown size
scopestring-Scope for scoping system
placementPlacement'bottom'Position relative to trigger
offsetOffsetOptions0Distance from trigger
allowFlipboolean | FlipPropstrueAllow flipping when out of space
stayInFrameboolean | ShiftPropstrueKeep within screen bounds
resizeboolean | SizePropsfalseResize to fit content

Component for triggering dropdown open. Extends StackProps.

PropTypeDefaultDescription
scopestring-Scope for connecting to Dropdown
asChildbooleanfalseUse child element as trigger

Component for displaying dropdown content.

PropTypeDefaultDescription
trapFocusbooleanfalseTrap focus within content
disableFocusScopebooleanfalseDisable focus management
onOpenAutoFocusFocusScopeProps['onMountAutoFocus']-Auto focus handler on open
onCloseAutoFocusFocusScopeProps['onUnmountAutoFocus'] | false-Auto focus handler on close
lazyMountbooleanfalseLazy load content
enableAnimationForPositionChangebooleanfalseEnable position change animation

Component for closing dropdown. Extends StackProps.

Component for custom anchor. Extends StackProps.

Component for scrolling content. Extends ScrollViewProps.

Component for focus management.

PropTypeDefaultDescription
enabledbooleantrueWhether focus management is enabled
loopbooleanfalseLoop through focusable elements
trappedbooleanfalseTrap focus
focusOnIdleboolean | numberfalseFocus when idle

Component for mobile adaptation. See Sheet documentation for details.

Types

type DropdownVia = 'hover' | 'press' type DropdownSizes = keyof ComponentsConfig['dropdown'] | (string & {}) type DropdownRect = { x: number y: number width: number height: number }