Skip to main content

Dropdown

Source ↗

A Dropdown is an interactive overlay menu anchored to a trigger element. It is built on top of the Popover component and is designed for action menus, navigation lists, and nested submenus.

Key Characteristics

  • Composable: Build menus declaratively using Dropdown.Menu, Dropdown.Section, Dropdown.Item, Dropdown.Divider, Dropdown.Header, and Dropdown.Footer.
  • Nestable: Create nested submenus with isNested - opens on hover with a caret indicator.
  • Polymorphic Items: Render items as any element (div, a, Link, etc.) using the as prop.
  • Accessible: Full keyboard navigation, focus management, and proper ARIA attributes built in.

Import

There are 7 dropdown-related components:

  • Dropdown: The main dropdown component.
  • DropdownTrigger: The element that toggles the dropdown.
  • DropdownMenu: The container for dropdown content.
  • DropdownItem: A clickable menu item.
  • DropdownSection: A group of items with an optional title.
  • DropdownDivider: A visual separator between sections.
  • DropdownHeader / DropdownFooter: Non-clickable top/bottom slots.
import {
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
DropdownSection,
DropdownDivider,
} from '@andrejground/lab';

Compound pattern is also supported:

  • Dropdown.Trigger
  • Dropdown.Menu
  • Dropdown.Item
  • Dropdown.Section
  • Dropdown.Divider
  • Dropdown.Header
  • Dropdown.Footer

Customization

Add your styles and your component is ready to be used.

import { Dropdown, DropdownProps } from '@andrejground/lab';
import styles from './MyDropdown.module.scss';

type Props = DropdownProps;

function MyDropdown(props: Props) {
return (
<Dropdown
{...props}
classNames={{
popover: { content: styles.popoverContent },
item: { base: styles.itemBase },
section: { base: styles.sectionBase, title: styles.sectionTitle },
divider: { base: styles.dividerBase },
}}
/>
);
}

MyDropdown.Menu = Dropdown.Menu;
MyDropdown.Header = Dropdown.Header;
MyDropdown.Footer = Dropdown.Footer;
MyDropdown.Section = Dropdown.Section;
MyDropdown.Item = Dropdown.Item;
MyDropdown.Trigger = Dropdown.Trigger;
MyDropdown.Divider = Dropdown.Divider;

export default MyDropdown;

Usage

Sections

Use Dropdown.Section to group items with titles, and Dropdown.Divider to visually separate groups.

Nested

Use isNested to create submenus. Nested dropdowns open on hover (and/or click) and display a caret indicator.

Keyboard navigation is fully supported between parent and child menus. Use Enter to open a nested menu, and Esc to close it.

Disabled Items

Use the disabled prop on Dropdown.Item to prevent interaction.

Polymorphic Items

Use the as prop to render items as different elements - links, anchors, or any other component.

Controlled

Open: false

API

PropTypeDefaultDescription
childrenReactNode-Dropdown content (expects Dropdown.Trigger and Dropdown.Menu)
triggerReactNode-Element that triggers the dropdown
shouldCloseOnSelectionbooleantrueWhether the dropdown closes after an item is selected
caretReactNode-Custom caret element replacing the default indicator
showCaretbooleanisNestedShows or hides the caret indicator
autoFocus"first-item" | "last-item" | "menu" | "none""menu"Controls which item receives focus when the dropdown opens
isOpenboolean-Controls the dropdown open state (controlled mode)
isDisabledboolean-Disables the dropdown trigger
isNestedbooleanfalseIndicates the dropdown is nested inside another dropdown
placementPopoverPlacementisNested ? "right-start" : "bottom-center"Preferred placement of the dropdown relative to the trigger
offsetnumber-Distance in pixels between the dropdown and the trigger
backdrop"none" | "transparent" | "opaque" | "blur"-The backdrop style displayed behind the dropdown
shouldFlipbooleantrueFlips placement when there is not enough space
shouldBlockScrollbooleantrueBlocks page scroll when the dropdown is open
shouldCloseOnScrollboolean!shouldBlockScrollCloses the dropdown when the page is scrolled
shouldCloseOnClickOutsidebooleantrueCloses the dropdown when clicking outside of it
shouldCloseOnEscbooleantrueCloses the dropdown when the Escape key is pressed
openOnHoverbooleanisNestedOpens the dropdown on hover instead of click
showArrowbooleanfalseShows an arrow pointing toward the trigger
growContentboolean-Allows the dropdown content to grow beyond the trigger width
onOpen() => void-Callback fired when the dropdown opens
onClose() => void-Callback fired when the dropdown closes
onClickOutside() => void-Callback fired when clicking outside the dropdown
onOpenChange(isOpen: boolean) => void-Callback fired when the open state changes
onTriggerFocus() => void-Callback fired when the trigger receives focus
onTriggerBlur() => void-Callback fired when the trigger loses focus
triggerWrapperboolean-Wraps the trigger in a span instead of using Slot
fullWidthTriggerWrapperboolean-Makes the trigger wrapper take full width
focusTrapProps{ trapFocus?: boolean; autoFocus?: boolean }{ trapFocus: true, autoFocus: autoFocus === "none" }Configuration for focus trap behavior
classNamesDropdownClassNames-Custom class names for the dropdown slots
PropTypeDefaultDescription
childrenReactNode-Trigger content
classNamesDropdownTriggerClassNames-Custom class names for the trigger slots
PropTypeDefaultDescription
childrenReactNode-Menu content (Dropdown.Section, Dropdown.Item, etc.)
classNamesDropdownMenuClassNames-Custom class names for the menu slots
PropTypeDefaultDescription
childrenReactNode-Section content
scrollingboolean-Enables scrolling within this section
titleReactNode-Title displayed above the section
isStickyTitlebooleantrueKeeps the section title visible while the section scrolls
infiniteScrollPropsInfiniteScrollProps-Configuration for infinite scrolling within this section
classNamesDropdownSectionClassNames-Custom class names for the section slots
PropTypeDefaultDescription
childrenReactNode-Item content
isHighlightedboolean-Visually highlights the item
shouldCloseOnSelectionboolean-Whether selecting this item closes the dropdown
disabledboolean-Disables the item
showDisabledStylesbooleandisabledShows disabled visual styles without disabling interaction
startContentReactNode-Content rendered before the item text
endContentReactNode-Content rendered after the item text
asElementType"div"Renders the item as a different HTML element or component
descriptionstring-Description text displayed below the item content
classNamesDropdownItemClassNames-Custom class names for the item slots
PropTypeDefaultDescription
childrenReactNode-Header content
isStickyboolean-Keeps the header visible while the menu scrolls
classNamesDropdownHeaderClassNames-Custom class names for the header slots
PropTypeDefaultDescription
childrenReactNode-Footer content
isStickyboolean-Keeps the footer visible while the menu scrolls
classNamesDropdownFooterClassNames-Custom class names for the footer slots
PropTypeDefaultDescription
classNamesDropdownDividerClassNames-Custom class names for the divider slots