Dropdown
The Dropdown component provides a versatile way to display a list of actions or options in a temporary, contextual menu that appears when a user interacts with a trigger element.
Installation
$ cbui-cli install dropdowncbui-cli globally and authenticated your account. This will ensure you can access all the necessary features.Import
1import {
2 Dropdown,
3 DropdownTrigger,
4 DropdownMenu,
5 DropdownItem,
6 DropdownSection,
7 DropdownContext,
8 useDropdownContext
9} from '@/crossbuildui/dropdown';
10import type { DropdownColor, DropdownSelectionMode, DropdownVariant } from '@/crossbuildui/dropdown'; // Optional
11import { Button } from '@/crossbuildui/button'; // Or your preferred triggerDropdownItem uses react-native-vector-icons/MaterialIcons. The glass variant for DropdownMenu uses expo-blur. Ensure these libraries are installed and linked if you rely on these features.Dropdown Components
Composition
The Dropdown system is built with a composable API, requiring specific child components:
Dropdown: The main wrapper component that orchestrates the behavior and state. It expects exactly two children:DropdownTriggerandDropdownMenu.DropdownTrigger: Wraps the element (e.g., a Button) that toggles the dropdown menu's visibility.DropdownMenu: Contains the list of options. It can hostDropdownItemandDropdownSectioncomponents as children, or render dynamic items from anitemsprop.DropdownItem: Represents an individual selectable option within the menu.DropdownSection: Allows grouping ofDropdownItems with an optional title and divider.
DropdownContext) to share state (like isOpen, selectedKeys) and props (like variant, color, theme information) between the main Dropdown, DropdownMenu, and its descendants (DropdownItem, DropdownSection).Basic Usage
A minimal dropdown consists of a trigger and a menu with items. The menu appears in a modal.
1function App() {
2 const [selectedKey, setSelectedKey] = useState<string | null>(null);
3
4 return (
5 <Dropdown>
6 <DropdownTrigger>
7 <CrossBuildButton>{selectedKey ? `Selected: ${selectedKey}` : "Open Menu"}</Button>
8 </DropdownTrigger>
9 <DropdownMenu
10 aria-label="Actions"
11 selectionMode="single"
12 selectedKeys={selectedKey ? [selectedKey] : []}
13 onSelectionChange={(keys) => setSelectedKey(Array.from(keys)[0] as string)}
14 >
15 <DropdownItem itemKey="edit">Edit File</DropdownItem>
16 <DropdownItem itemKey="duplicate">Duplicate File</DropdownItem>
17 <DropdownItem itemKey="delete" color="danger">Delete File</DropdownItem>
18 </DropdownMenu>
19 </Dropdown>
20 );
21}Selection Modes
The DropdownMenu supports different selection behaviors via the selectionMode prop:
none(default): Items are not selectable; they act as simple actions.single: Only one item can be selected at a time.multiple: Multiple items can be selected.
Use selectedKeys (controlled) or defaultSelectedKeys (uncontrolled) and onSelectionChange to manage selection. The disallowEmptySelection prop (boolean) can prevent deselection if it would result in no items being selected (relevant for single and multiple modes). The closeOnSelect prop (boolean, defaults to true) on DropdownMenu determines if the menu closes after an item is selected.
1function App() {
2 const [singleSelected, setSingleSelected] = useState(new Set(['cat']));
3 const [multipleSelected, setMultipleSelected] = useState(new Set(['lion', 'tiger']));
4
5 return (
6 <View style={{ gap: 20 }}>
7 <View>
8 <Text style={{ marginBottom: 5 }}>Single Selection (selected: {Array.from(singleSelected).join(", ")})</Text>
9 <Dropdown>
10 <DropdownTrigger><Button>Select Animal</Button></DropdownTrigger>
11 <DropdownMenu
12 aria-label="Single selection example"
13 selectionMode="single"
14 selectedKeys={singleSelected}
15 onSelectionChange={setSingleSelected}
16 disallowEmptySelection // Example: must select one
17 >
18 <DropdownItem itemKey="dog">Dog</DropdownItem>
19 <DropdownItem itemKey="cat">Cat</DropdownItem>
20 <DropdownItem itemKey="mouse">Mouse</DropdownItem>
21 </DropdownMenu>
22 </Dropdown>
23 </View>
24
25 <View>
26 <Text style={{ marginBottom: 5 }}>Multiple Selection (selected: {Array.from(multipleSelected).join(", ")})</Text>
27 <Dropdown>
28 <DropdownTrigger><Button>Select Animals</Button></DropdownTrigger>
29 <DropdownMenu
30 aria-label="Multiple selection example"
31 selectionMode="multiple"
32 selectedKeys={multipleSelected}
33 onSelectionChange={setMultipleSelected}
34 closeOnSelect={false} // Keep menu open for multiple selections
35 >
36 <DropdownItem itemKey="lion">Lion</DropdownItem>
37 <DropdownItem itemKey="tiger">Tiger</DropdownItem>
38 <DropdownItem itemKey="bear">Bear</DropdownItem>
39 <DropdownItem itemKey="snake" isDisabled>Snake (Disabled)</DropdownItem>
40 </DropdownMenu>
41 </Dropdown>
42 </View>
43 </View>
44 );
45}Sections
Group related items using DropdownSection. It can have a title and an optional showDivider prop. Section-specific itemStyle and hideSelectedIcon can also be applied.
1function App() {
2 return (
3 <Dropdown>
4 <DropdownTrigger><Button>Actions</Button></DropdownTrigger>
5 <DropdownMenu aria-label="Actions with sections">
6 <DropdownSection title="File Operations" itemKey="s1">
7 <DropdownItem itemKey="new">New File</DropdownItem>
8 <DropdownItem itemKey="copy">Copy Link</DropdownItem>
9 </DropdownSection>
10 <DropdownSection title="Danger Zone" showDivider itemKey="s2">
11 <DropdownItem itemKey="delete" color="danger">Delete File</DropdownItem>
12 </DropdownSection>
13 </DropdownMenu>
14 </Dropdown>
15 );
16}Dynamic Items
Render items dynamically by passing an array to the items prop of DropdownMenu and providing a render function as its child or via the renderItem prop.
1const fileOptions = [
2 { id: 'doc1', name: 'Document A.pdf', type: 'file' },
3 { id: 'img1', name: 'Image X.png', type: 'image', disabled: true },
4 { id: 'vid1', name: 'Video Y.mp4', type: 'video' },
5];
6
7function App() {
8 return (
9 <Dropdown>
10 <DropdownTrigger><Button>Open Dynamic Menu</Button></DropdownTrigger>
11 <DropdownMenu
12 aria-label="Dynamic items"
13 items={fileOptions}
14 onSelectionChange={(keys) => console.log('Selected:', keys)}
15 >
16 {(item) => (
17 <DropdownItem
18 itemKey={item.id}
19 title={item.name}
20 description={`Type: ${item.type}`}
21 isDisabled={item.disabled}
22 startContent={<Text>{item.type === 'file' ? '📄' : item.type === 'image' ? '🖼️' : '🎞️'}</Text>}
23 />
24 )}
25 </DropdownMenu>
26 </Dropdown>
27 );
28}Menu Variants & Colors
The DropdownMenu supports several visual styles through the variant prop (solid, bordered, light, flat, faded, shadow, glass) and thematic coloring via the color prop (default, primary, etc.).
1const variants: DropdownVariant[] = ['solid', 'bordered', 'light', 'flat', 'faded', 'shadow', 'glass'];
2const colors: DropdownColor[] = ['default', 'primary', 'secondary', 'success', 'warning', 'danger'];
3
4function App() {
5 return (
6 <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 16 }}>
7 {variants.map(variant => (
8 colors.map(color => (
9 <Dropdown key={`${variant}-${color}`}>
10 <DropdownTrigger>
11 <Button size="sm" style={{ paddingHorizontal: 8, paddingVertical: 4 }}>
12 {variant} - {color}
13 </Button>
14 </DropdownTrigger>
15 <DropdownMenu
16 variant={variant}
17 color={color}
18 aria-label={`${variant} ${color} menu`}
19 {...(variant === 'glass' && { glassIntensity: 70, glassTint: 'light' })} // Example glass props
20 >
21 <DropdownItem itemKey="action1">Action 1</DropdownItem>
22 <DropdownItem itemKey="action2">Action 2</DropdownItem>
23 </DropdownMenu>
24 </Dropdown>
25 ))
26 ))}
27 </View>
28 );
29}Glass Variant Menu
The glass variant for DropdownMenu applies a frosted glass effect to the menu background using expo-blur. Customize it with glassIntensity (0-100) and glassTint ('light', 'dark', 'default'). Item text colors adapt for better contrast on the blurred background, but you might need to adjust them manually for certain tints using slotStyles on DropdownItem. The modal backdrop for a glass menu has a very subtle dark tint.
1function App() {
2 return (
3 <View style={{ flexDirection: 'row', gap: 20 }}>
4 <Dropdown>
5 <DropdownTrigger><Button variant="glass">Light Glass Menu</Button></DropdownTrigger>
6 <DropdownMenu variant="glass" glassIntensity={80} glassTint="light" aria-label="Light glass menu">
7 <DropdownItem itemKey="profile">Profile</DropdownItem>
8 <DropdownItem itemKey="settings">Settings</DropdownItem>
9 </DropdownMenu>
10 </Dropdown>
11
12 <Dropdown>
13 <DropdownTrigger><Button variant="glass">Dark Glass Menu</Button></DropdownTrigger>
14 <DropdownMenu variant="glass" glassIntensity={50} glassTint="dark" aria-label="Dark glass menu">
15 {/* Item text color might need manual adjustment for dark glass if default doesn't contrast well */}
16 <DropdownItem itemKey="dashboard" slotStyles={{ title: { color: 'white' } }}>Dashboard</DropdownItem>
17 <DropdownItem itemKey="logout" color="danger" slotStyles={{ title: { color: 'pink' } }}>Logout</DropdownItem>
18 </DropdownMenu>
19 </Dropdown>
20 </View>
21 );
22}Custom Item Content
DropdownItem offers props like description, startContent, and endContent to enrich item appearance. The selectedIcon prop allows for custom selection indicators. If hideSelectedIcon is true (can be set on Menu, Section, or Item), the icon is hidden.
1function App() {
2 return (
3 <Dropdown>
4 <DropdownTrigger><Button>User Profile</Button></DropdownTrigger>
5 <DropdownMenu aria-label="User actions">
6 <DropdownItem
7 itemKey="profile"
8 title="Signed in as"
9 description="zoey@example.com"
10 textValue="User Profile Zoey" // For accessibility
11 startContent={<View style={{ width: 24, height: 24, borderRadius: 12, backgroundColor: 'skyblue' }} />}
12 />
13 <DropdownItem itemKey="settings" startContent={<Text>⚙️</Text>}>Settings</DropdownItem>
14 <DropdownItem itemKey="logout" color="danger" endContent={<Text>➡️</Text>}>
15 Log Out
16 </DropdownItem>
17 </DropdownMenu>
18 </Dropdown>
19 );
20}Using Dropdown Context
For advanced scenarios, such as creating custom components that need to interact with or display the Dropdown's state, you can use the useDropdownContext hook. This hook provides access to the DropdownContext, which includes properties like isOpen, selectedKeys, variant, color, selectionMode, and methods to control the dropdown.
Any component calling useDropdownContext must be a descendant of a Dropdown component. This is particularly useful for custom content within DropdownMenu (e.g., in topContent or bottomContent) or for building highly integrated custom items.
1import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, useDropdownContext } from '@/crossbuildui/dropdown';
2import { Button } from '@/crossbuildui/button';
3import { Text, View } from 'react-native';
4
5const MenuStatusIndicator = () => {
6 const context = useDropdownContext();
7
8 if (!context) {
9 return <Text>This component must be used within a Dropdown.</Text>;
10 }
11
12 const { isOpen, variant, color, selectedKeys } = context;
13
14 return (
15 <View style={{ padding: 8, backgroundColor: isOpen ? '#e6ffe6' : '#f0f0f0', marginVertical: 4 }}>
16 <Text>Menu is: {isOpen ? 'Open' : 'Closed'}</Text>
17 <Text>Variant: {variant}, Color: {color}</Text>
18 <Text>Selected Keys: {Array.from(selectedKeys).join(', ') || 'None'}</Text>
19 </View>
20 );
21};
22
23// <Dropdown><DropdownTrigger><Button>Open Menu</Button></DropdownTrigger><DropdownMenu><MenuStatusIndicator /><DropdownItem itemKey="1">Item 1</DropdownItem></DropdownMenu></Dropdown>
24// Note: MenuStatusIndicator would typically be part of topContent or bottomContent for better layout.Props Overview
Dropdown Props
| PROP | TYPE | DESCRIPTION |
|---|---|---|
children | [Trigger, Menu] | Must be DropdownTrigger followed by DropdownMenu. |
DropdownTrigger Props
| PROP | TYPE | DESCRIPTION |
|---|---|---|
children | ReactNode | The trigger element (e.g., Button). |
style | StyleProp<ViewStyle> | Style for the trigger wrapper View. |
DropdownMenu Props
| PROP | TYPE | DEFAULT | DESCRIPTION |
|---|---|---|---|
children | ReactNode | (item) => Element | - | Static items or render function for dynamic items. |
items | T[] | - | Array of data for dynamic rendering. |
renderItem | (item: T) => Element | - | Render function for dynamic items if children is not a function. |
variant | DropdownVariant | 'solid' | Visual style of the menu. |
color | DropdownColor | 'default' | Thematic color of the menu. |
selectionMode | DropdownSelectionMode | 'none' | Selection behavior. |
selectedKeys | Iterable<string|number> | - | Controlled selected item keys. |
defaultSelectedKeys | Iterable<string|number> | [] | Uncontrolled initial selected keys. |
disabledKeys | Iterable<string|number> | [] | Keys of items to disable. |
disallowEmptySelection | boolean | false | Prevent deselection to an empty state. |
topContent | ReactNode | - | Content to display above the items. |
bottomContent | ReactNode | - | Content to display below the items. |
emptyContent | ReactNode | Default Text | Content when no items are available. |
hideSelectedIcon | boolean | false | Hide selection indicator for all items in this menu. |
closeOnSelect | boolean | Context-based (true) | Whether to close the menu on item selection. |
disableAnimation | boolean | false | Disable menu open/close animation. |
style | StyleProp<ViewStyle> | - | Style for the menu container. |
itemStyle | Partial<DropdownItemSlots> | - | Default styles for direct child item slots. |
glassTint | 'default' | 'light' | 'dark' | Theme-derived | Tint for the 'glass' variant blur effect. |
glassIntensity | number | 50 | Intensity (0-100) for the 'glass' variant blur effect. |
onSelectionChange | (keys: Set) => void | - | Callback when selection changes. |
DropdownItem Props
| PROP | TYPE | DEFAULT | DESCRIPTION |
|---|---|---|---|
children | ReactNode | - | Primary content, overrides title. |
itemKey | string | number | Required | Unique key for the item. |
title | ReactNode | - | Title of the item if children not provided. |
textValue | string | - | Plain text for accessibility/typeahead. |
description | ReactNode | - | Additional descriptive text. |
startContent | ReactNode | - | Content to render before the title/description. |
endContent | ReactNode | - | Content to render after the selected icon. |
selectedIcon | ReactNode | (props) => Node | Default Check Icon | Custom selection indicator. |
showDivider | boolean | false | Show a divider after this item. |
hideSelectedIcon | boolean | Context-based | Override menu/section setting for this item. |
style | StyleProp<ViewStyle> | - | Style for the item's outer Pressable. |
slotStyles | Partial<DropdownItemSlots> | - | Styles for inner parts of the item. |
isDisabled | boolean | false | Explicitly disable this item. |
DropdownSection Props
| PROP | TYPE | DEFAULT | DESCRIPTION |
|---|---|---|---|
children | ReactNode | Required | DropdownItems within this section. |
itemKey | string | number | Required | Unique key for the section. |
title | string | - | Title for the section. |
hideSelectedIcon | boolean | Context-based | Override menu setting for items in this section. |
showDivider | boolean | false | Show a divider after the section. |
dividerProps | StyleProp<ViewStyle> | - | Style for the section divider. |
style | StyleProp<ViewStyle> | - | Style for the section wrapper. |
itemStyle | Partial<DropdownItemSlots> | - | Default styles for item slots in this section. |
Styling
The Dropdown components offer various styling capabilities:
DropdownTrigger: Accepts astyleprop for its wrapperView.DropdownMenu: Thestyleprop targets the main menu container. Thevariantandcolorprops apply predefined visual styles. For theglassvariant,glassTintandglassIntensitycontrol the blur. The menu background becomes transparent, and aBlurViewis rendered underneath. TheitemStyleprop allows defining default styles for allDropdownItemSlotswithin this menu (can be overridden by Section or Item).DropdownSection: Thestyleprop targets the section's wrapperView. TheitemStyleprop sets default styles forDropdownItemSlotswithin this section (overrides Menu, can be overridden by Item).dividerPropsstyles the optional divider.DropdownItem: Thestyleprop targets the outerPressablecomponent. TheslotStylesprop provides granular styling for internal parts likebase,wrapper,title,description, etc. (seeDropdownItemSlotstype).
DropdownItem styling, the precedence is:1. Direct
slotStyles on DropdownItem.2.
itemStyle from parent DropdownSection.3.
itemStyle from parent DropdownMenu.4. Default styles within
DropdownItem.The
style prop on DropdownItem always applies to its root Pressable.1function App() {
2 return (
3 <Dropdown>
4 <DropdownTrigger style={{ borderWidth: 1, borderColor: 'blue', padding: 5, borderRadius: 6 }}>
5 <Button variant="ghost">Styled Trigger</Button>
6 </DropdownTrigger>
7 <DropdownMenu
8 aria-label="Styled Menu"
9 style={{ borderColor: 'green', borderWidth: 2, borderRadius: 12 }} // Menu container style
10 itemStyle={{ // Default item styles for this menu
11 base: { paddingVertical: 15 },
12 title: { fontWeight: 'bold', color: 'purple' },
13 }}
14 >
15 <DropdownItem itemKey="custom" title="Custom Item Style" slotStyles={{
16 wrapper: { backgroundColor: 'lightyellow' },
17 description: { fontStyle: 'italic', color: 'orange' }
18 }} description="This item has specific slot styles." />
19 <DropdownSection itemKey="s1" title="Styled Section" style={{ backgroundColor: '#f0f0f0', marginTop: 5 }} itemStyle={{
20 base: { borderLeftWidth: 3, borderLeftColor: 'red' }
21 }}>
22 <DropdownItem itemKey="sectioned">Item in Styled Section</DropdownItem>
23 </DropdownSection>
24 </DropdownMenu>
25 </Dropdown>
26 );
27}Accessibility
The Dropdown component aims to be accessible:
- The menu is rendered within a
Modal, which typically handles focus trapping. DropdownItemsetsaccessibilityState={{ selected, disabled }}.- Provide a meaningful
textValueprop onDropdownItemfor better screen reader support, especially iftitleorchildrenare complex. This value is used foraccessibilityLabel. - The
DropdownMenushould have anaria-labelprop passed to it for context. - The trigger element should clearly indicate its purpose.