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 @crossbuildui/dropdown
cbui-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} from '@/crossbuildui/dropdown';
8import type { DropdownColor, DropdownSelectionMode, DropdownVariant } from '@/crossbuildui/dropdown'; // Optional
9import { Button } from '@/crossbuildui/button'; // Or your preferred trigger
DropdownItem
uses react-native-vector-icons/MaterialIcons
. Ensure this library is installed and linked if you rely on the default icon. You can provide your own custom selectedIcon
to avoid this dependency.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:DropdownTrigger
andDropdownMenu
.DropdownTrigger
: Wraps the element (e.g., a Button) that toggles the dropdown menu's visibility.DropdownMenu
: Contains the list of options. It can hostDropdownItem
andDropdownSection
components as children, or render dynamic items from anitems
prop.DropdownItem
: Represents an individual selectable option within the menu.DropdownSection
: Allows grouping ofDropdownItem
s 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"}</CrossBuildButton>
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><CrossBuildButton>Select Animal</CrossBuildButton></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><CrossBuildButton>Select Animals</CrossBuildButton></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><CrossBuildButton>Actions</CrossBuildButton></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><CrossBuildButton>Open Dynamic Menu</CrossBuildButton></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
) and thematic coloring via the color
prop (default
, primary
, etc.).
1const variants: DropdownVariant[] = ['solid', 'bordered', 'light', 'flat', 'faded', 'shadow'];
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 <CrossBuildButton size="sm" style={{ paddingHorizontal: 8, paddingVertical: 4 }}>
12 {variant} - {color}
13 </CrossBuildButton>
14 </DropdownTrigger>
15 <DropdownMenu variant={variant} color={color} aria-label={`${variant} ${color} menu`}>
16 <DropdownItem itemKey="action1">Action 1</DropdownItem>
17 <DropdownItem itemKey="action2">Action 2</DropdownItem>
18 </DropdownMenu>
19 </Dropdown>
20 ))
21 ))}
22 </View>
23 );
24}
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><CrossBuildButton>User Profile</CrossBuildButton></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}
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. |
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 astyle
prop for its wrapperView
.DropdownMenu
: Thestyle
prop targets the main menu container. Thevariant
andcolor
props apply predefined visual styles. TheitemStyle
prop allows defining default styles for allDropdownItemSlots
within this menu (can be overridden by Section or Item).DropdownSection
: Thestyle
prop targets the section's wrapperView
. TheitemStyle
prop sets default styles forDropdownItemSlots
within this section (overrides Menu, can be overridden by Item).dividerProps
styles the optional divider.DropdownItem
: Thestyle
prop targets the outerPressable
component. TheslotStyles
prop provides granular styling for internal parts likebase
,wrapper
,title
,description
, etc. (seeDropdownItemSlots
type).
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 <CrossBuildButton variant="ghost">Styled Trigger</CrossBuildButton>
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. DropdownItem
setsaccessibilityState={{ selected, disabled }}
.- Provide a meaningful
textValue
prop onDropdownItem
for better screen reader support, especially iftitle
orchildren
are complex. This value is used foraccessibilityLabel
. - The
DropdownMenu
should have anaria-label
prop passed to it for context. - The trigger element should clearly indicate its purpose.