DropDown Menu

A dropdown menu displays a list of options for the element that triggers it.

installyarn add @clayui/drop-down
versionNPM Version
useimport DropDown from '@clayui/drop-down';

Example

import {Provider} from '@clayui/core';
import DropDown from '@clayui/drop-down';
import Button from '@clayui/button';
import React from 'react';

import '@clayui/css/lib/css/atlas.css';

const items = [
	{
		children: [
			{id: 2, name: 'Apple'},
			{id: 3, name: 'Banana'},
			{id: 4, name: 'Mangos'},
		],
		id: 1,
		name: 'Fruit',
	},
	{
		children: [
			{id: 6, name: 'Potatoes'},
			{id: 7, name: 'Tomatoes'},
			{id: 8, name: 'Onions'},
		],
		id: 5,
		name: 'Vegetable',
	},
];

export default function App() {
	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<DropDown
					filterKey="name"
					trigger={<Button>Select</Button>}
					triggerIcon="caret-bottom"
				>
					<DropDown.Search placeholder="Type to filter" />
					<DropDown.ItemList items={items}>
						{(item) => (
							<DropDown.Group
								header={item.name}
								items={item.children}
								key={item.name}
							>
								{(item) => (
									<DropDown.Item
										key={item.name}
										onClick={() => {
											// logic stuff...
										}}
									>
										{item.name}
									</DropDown.Item>
								)}
							</DropDown.Group>
						)}
					</DropDown.ItemList>
				</DropDown>
			</div>
		</Provider>
	);
}

Introduction

The Dropdown Menu component is designed to display menus that when clicked triggers some action, there are different forms of Menu in Clay/Lexicon. This document describes the main ways to build a menu and recommendations.

Clay provides two possible possibilities to build a Menu, using composition that we call low-level that allows you to add new use cases and adapt them to your use case and components like high-level which are property-setting oriented components. We recommend that you always try to use compositing when you need flexibility, whether it’s configuring some internal components that aren’t possible in a high-level component or adding your own elements to an item, group, or other use cases that don’t exist in Clay.

Content

Dropdown Menu provides two options for building the menu using static content when data does not change during an application’s lifecycle and dynamic for content that changes during the application lifecycle or the data comes from the server.

Static

The simplest example of a static menu is rendering a simple list of options.

<DropDown trigger={<Button>Click Me</Button>}>
	<DropDown.ItemList>
		<DropDown.Item>one</DropDown.Item>
		<DropDown.Item>two</DropDown.Item>
		<DropDown.Item>three</DropDown.Item>
	</DropDown.ItemList>
</DropDown>

Other cases are also possible, for example rendering a group of options with a search bar to filter the options.

<DropDown trigger={<Button>Click Me</Button>}>
	<DropDown.Search placeholder="Type to filter" />
	<DropDown.ItemList>
		<DropDown.Group header="Numbers">
			<DropDown.Item>one</DropDown.Item>
			<DropDown.Item>two</DropDown.Item>
			<DropDown.Item>three</DropDown.Item>
		</DropDown.Group>
		<DropDown.Group header="Letters">
			<DropDown.Item>a</DropDown.Item>
			<DropDown.Item>b</DropDown.Item>
			<DropDown.Item>c</DropDown.Item>
		</DropDown.Group>
	</DropDown.ItemList>
</DropDown>

The example above shows how Clay even with the explicit composition of the menu structure manages to provide the filter to work OOTB with static content.

Dynamic

<DropDown items={['one', 'two', 'three']} trigger={<Button>Click Me</Button>}>
	{(item) => <DropDown.Item>{item}</DropDown.Item>}
</DropDown>

Compositing for dynamic content becomes simpler and also provides the same features as static, such as OOTB filter and groups. The component is also data agnostic designed from the ground up for this purpose to avoid transformations having to be done at render time.

<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
	<DropDown.Search placeholder="Type to filter" />
	<DropDown.ItemList
		items={[
			{
				children: [{name: 'one'}, {name: 'two'}, {name: 'three'}],
				name: 'Numbers',
			},
			{
				children: [{name: 'a'}, {name: 'b'}, {name: 'c'}],
				name: 'Letters',
			},
		]}
	>
		{(item) => (
			<DropDown.Group
				header={item.name}
				items={item.children}
				key={item.name}
			>
				{(item) => (
					<DropDown.Item key={item.name}>{item.name}</DropDown.Item>
				)}
			</DropDown.Group>
		)}
	</DropDown.ItemList>
</DropDown>

Filter

The filter in a Menu works OOTB, just declaring the <DropDown.Search> component in the composition, as in the examples shown for static and dynamic content. The Filter can be done by DropDown itself as well as the developer can control the filter and make his own filter rule.

function Example() {
	const [value, setValue] = useState('');

	const options = [{name: 'one'}, {name: 'two'}, {name: 'three'}];

	const filteredOptions = useMemo(() => {
		if (!value) {
			return options;
		}

		return options.filter(
			(option) => option.match(new RegExp(value, 'i')) !== null
		);
	}, [options, value]);

	return (
		<DropDown trigger={<Button>Click Me</Button>}>
			<DropDown.Search
				value={value}
				onChange={setValue}
				placeholder="Type to filter"
			/>
			<DropDown.ItemList items={filteredOptions}>
				{(item) => (
					<DropDown.Item key={item.name}>{item.name}</DropDown.Item>
				)}
			</DropDown.ItemList>
		</DropDown>
	);
}

If you just need to set an initial value, just use the defaultValue property.

<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
	<DropDown.Search defaultValue="o" placeholder="Type to filter" />
	<DropDown.ItemList items={[{name: 'one'}, {name: 'two'}, {name: 'three'}]}>
		{(item) => <DropDown.Item key={item.name}>{item.name}</DropDown.Item>}
	</DropDown.ItemList>
</DropDown>

Value

DropDown filters items according to the value rendered as children of <DropDown.Item>, when there are more detailed compositions in the item, the filter will not work because it cannot determine which value to use to filter, in this scenario configure the item’s value using the textValue property.

<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
	<DropDown.Search placeholder="Type to filter" />
	<DropDown.ItemList items={[{name: 'one'}, {name: 'two'}, {name: 'three'}]}>
		{(item) => (
			<DropDown.Item key={item.name} textValue={item.name}>
				<Checkbox />
				{item.name}
			</DropDown.Item>
		)}
	</DropDown.ItemList>
</DropDown>

Without Trigger

You may want to create a trigger that is not necessarily in the same tree as DropDown, due to HTML markup issues, for these cases you can use <ClayDropDown.Menu>.

Using <ClayDropDown.Menu> allows you to better control the state of DropDown but you will have to deal with visibility, focus management, and other details.

As a recommendation only use this component as a last resort, it doesn’t provide some OOTB features like focus control or accessibility at all.

import ClayDropDown from '@clayui&#x2215;drop-down';
import React, {useState, useRef} from 'react';

const Menu = ({children, hasLeftSymbols, hasRightSymbols}) => {
	const triggerElementRef = useRef(null);
	const [expand, setExpand] = useState(false);
	const menuElementRef = useRef(null);

	const handleExpand = (event) => {
		// This is not ideal for allowing you to have more than
		// one trigger for the same content but it simulates the
		// advantages of controlling `DropDown.Menu`.
		triggerElementRef.current = event.target;

		setExpand(!expand);
	};

	return (
		<div>
			<div>
				<button type="button" onClick={handleExpand}>
					Home
				</button>
			</div>
			<div>
				<button type="button" onClick={handleExpand}>
					Product
				</button>
			</div>

			<ClayDropDown.Menu
				active={expand}
				alignElementRef={triggerElementRef}
				hasLeftSymbols={hasLeftSymbols}
				hasRightSymbols={hasRightSymbols}
				onActiveChange={() => setExpand(!expand)}
				ref={menuElementRef}
			>
				{children}
			</ClayDropDown.Menu>
		</div>
	);
};

Variants

Clay provides other ways to use the Clay component, which we call high-level components, which are designed to do a specific high-level behavior different from compositing that allow different possibilities, are less flexible.

Icon

To indicate a dropdown menu and improve user understanding, set the triggerIcon property and it will display the icon chosen alongside the button text to indicate the dropdown functionality.

The default value is null.

With Items

Allows you to create a simple DropDown, through its API you are able to create a Menu with groups of checkboxes and radios, links, buttons, search, caption, etc.

import {Provider} from '@clayui/core';
import DropDown from '@clayui/drop-down';
import Button from '@clayui/button';
import React, {useState} from 'react';

import '@clayui/css/lib/css/atlas.css';

const items = [
	{
		label: 'clickable',
		onClick: (event) => event.preventDefault(),
	},
	{
		type: 'divider',
	},
	{
		items: [
			{
				label: 'one',
				type: 'radio',
				value: 'one',
			},
			{
				label: 'two',
				type: 'radio',
				value: 'two',
			},
		],
		label: 'radio',
		name: 'radio',
		onChange: (value) => alert('New Radio checked'),
		type: 'radiogroup',
	},
	{
		items: [
			{
				checked: true,
				label: 'checkbox',
				onChange: () => alert('checkbox changed'),
				type: 'checkbox',
			},
			{
				checked: true,
				label: 'checkbox 1',
				onChange: () => alert('checkbox changed'),
				type: 'checkbox',
			},
		],
		label: 'checkbox',
		type: 'group',
	},
	{
		href: '#',
		label: 'linkable',
	},
];

export default function App() {
	const [value, setValue] = useState();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<DropDown
					filterKey="name"
					trigger={<Button>Select</Button>}
					triggerIcon="caret-bottom"
				>
					<DropDown.Search placeholder="Type to filter" />
					<DropDown.ItemList items={items}>
						{(item) => (
							<DropDown.Group
								header={item.name}
								items={item.children}
								key={item.name}
							>
								{(item) => (
									<DropDown.Item
										key={item.name}
										onClick={() => {
											// logic stuff...
										}}
									>
										{item.name}
									</DropDown.Item>
								)}
							</DropDown.Group>
						)}
					</DropDown.ItemList>
				</DropDown>
			</div>
		</Provider>
	);
}

Customizable Trigger Icon

When using DropDownWithItems, you can customize the icon on the dropdown button via the triggerIcon prop.

Cascading Menu

DropDownWithItems allows the possibility to create a contextual menu, the nature of the API allows the creation of more cascade menus but the Lexicon specification recommends using only one level and using DropDownWithDrilldown component.

To render a cascading menu it is necessary to set the type of the item to contextual and add to the items object that follows the same API as the other items.

const items = [
	{label: 'Folder'},
	{type: 'divider'},
	{
		items: [
			{label: 'Basic Document'},
			{label: 'Contract'},
			{label: 'Marketing Banner'},
			{label: 'Spreadsheet'},
			{label: 'Presentation'},
		],
		label: 'Document',
		type: 'contextual',
	},
	{label: 'Shortcut'},
	{label: 'Repository'},
];

Drilldown

A Drilldown menu allows the user to navigate to and/or select an element from a contextual list. It can be triggered by a dropdown button.

import {Provider} from '@clayui/core';
import {ClayDropDownWithDrilldown} from '@clayui/drop-down';
import Button from '@clayui/button';
import React from 'react';

import '@clayui/css/lib/css/atlas.css';

export default function App() {
	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<ClayDropDownWithDrilldown
					defaultActiveMenu="x0a3"
					menus={{
						x0a3: [
							{href: '#', title: 'Hash Link'},
							{type: 'divider'},
							{onClick: () => alert('test'), title: 'Alert!'},
							{type: 'divider'},
							{child: 'x0a4', title: 'Subnav'},
						],
						x0a4: [
							{href: '#', title: '2nd hash link'},
							{child: 'x0a5', title: 'Subnav'},
						],
						x0a5: [
							{title: 'The'},
							{type: 'divider'},
							{title: 'End'},
						],
					}}
					trigger={<Button>{'Click Me'}</Button>}
				/>
			</div>
		</Provider>
	);
}

The way the Drilldown component links the menus is done via reference, the menu needs a unique id.

const menus = {
	of23: [{title: 'First'}],
};

From the id you are able to link to another menu using the child property.

const menus = {
	of23: [{title: 'First', child: 'of09'}],
	of09: [{title: 'Three'}],
};

If you want to add separators between your menu items, it’s possible to do so, by using an item that has this shape {type: 'divider'}.

const menus = {
	of23: [
		{title: 'First', child: 'of09'},
		{type: 'divider'},
		{title: 'Second'},
	],
	of09: [{title: 'Three'}],
};

An important thing to have in mind, is that the DropDownWithDrilldown component, will render its menus in the order that they are specified. This means that if you specify the menus in the wrong order, the menu animation will not behave correctly.

Caveats

One caveat with the drop down menu is that it is rendered inside of a React Portal and is rendered directly to the body element. This means if you are using the menuWidth prop set to auto, it will not respect the size of the node parent of the drop down, since the menu is rendered directly to the body.

API Reference

typeof DropDown
Parameters
Properties

children *

React.ReactNode | ((item: T, index?: number) => React.ReactElement)

Children content to render a dynamic or static content.

items

Array<T> | undefined

Property to render content with dynamic data.

active

boolean | undefined

Flag to indicate if the DropDown menu is active or not (controlled).

This API is generally used in conjunction with closeOnClickOutside=true since often we are controlling the active state by clicking another element within the document.

alignmentByViewport

boolean | undefined

Flag to align the DropDown menu within the viewport.

alignmentPosition

any

Default position of menu element. Values come from ./Menu.

containerElement

React.JSXElementConstructor<any> | "div" | "li" | undefined= "div"

HTML element tag that the container should render.

closeOnClick

boolean | undefined

Flag that indicates whether to close DropDown when clicking on the item.

closeOnClickOutside

boolean | undefined

defaultActive

boolean | undefined

Property to set the default value of active (uncontrolled).

filterKey

string | undefined

Defines the name of the property key that is used in the items filter test (Dynamic content).

hasRightSymbols

boolean | undefined

Flag to indicate if menu contains icon symbols on the right side.

hasLeftSymbols

boolean | undefined

Flag to indicate if menu contains icon symbols on the left side.

menuElementAttrs

any

Prop to pass DOM element attributes to DropDown.Menu.

menuHeight

"auto" | undefined

Adds utility class name dropdown-menu-height-${height}

menuWidth

"sm" | "shrink" | "full" | undefined

The modifier class dropdown-menu-width-${width} makes the menu expand the full width of the page.

  • sm makes the menu 500px wide.
  • shrink makes the menu auto-adjust to text and max 240px wide.
  • full makes the menu 100% wide.

onActiveChange

any

Callback for when the active state changes (controlled).

This API is generally used in conjunction with closeOnClickOutside=true since often we are controlling the active state by clicking another element within the document.

offsetFn

((points: AlignPoints) => [number, number]) | undefined

Function for setting the offset of the menu from the trigger.

renderMenuOnClick

boolean | undefined

Flag indicating if the menu should be rendered lazily

triggerIcon

string | null | undefined

Flag indicating if the caret icon should be displayed on the right side.

trigger *

React.ReactElement<any, string | React.JSXElementConstructor<any>> & { ref?: (node: HTMLButtonElement | null) => void; }

Element that is used as the trigger which will activate the dropdown on click.

Returns
Element

Action

({ children, className, ...otherProps }: React.HTMLAttributes<HTMLButtonElement>) => JSX.Element
Returns
Element

Item

React.ForwardRefExoticComponent<IProps & React.RefAttributes<HTMLLIElement>>
Parameters
Properties

active

boolean | undefined

Flag that indicates if item is selected.

disabled

boolean | undefined

Flag that indicates if item is disabled or not.

href

string | undefined

Path for item to link to.

innerRef

React.Ref<any> | undefined

roleItem

string | undefined

Sets the role accessibility property of the item. Set the item's container (

  • ) role use the role="" prop instead of roleItem="".

    spritemap

    string | undefined

    Path to icon spritemap from clay-css.

    symbolLeft

    string | undefined

    Flag that indicates if there is an icon symbol on the left side.

    symbolRight

    string | undefined

    Flag that indicates if there is an icon symbol on the right side.

    Returns
    ReactElement<any, string | JSXElementConstructor<any>> | null

    ItemList

    React.ForwardRefExoticComponent<Pick<IProps<unknown>, keyof IProps<unknown>> & React.RefAttributes<HTMLUListElement>>

    Section

    React.ForwardRefExoticComponent<IProps & React.RefAttributes<HTMLLIElement>>
    Parameters
    Properties

    active

    boolean | undefined

    Flag that indicates if item is selected.

    disabled

    boolean | undefined

    Flag that indicates if item is disabled or not.

    innerRef

    React.Ref<any> | undefined
    Returns
    ReactElement<any, string | JSXElementConstructor<any>> | null

    Group

    typeof Group
    Parameters
    Properties

    header

    string | undefined

    Value provided is a display component that is a header for the items in the group.

    role

    string | undefined= "group"

    ARIA to define semantic meaning to content.

    Returns
    Element

    Help

    ({ children, className, ...otherProps }: React.HTMLAttributes<HTMLDivElement>) => JSX.Element
    Returns
    Element

    Caption

    ({ children, className, ...otherProps }: React.HTMLAttributes<HTMLDivElement>) => JSX.Element
    Returns
    Element

    ClayDropDownWithDrilldown

    { ({ active: externalActive, alignmentByViewport, alignmentPosition, className, containerElement, defaultActive, defaultActiveMenu, initialActiveMenu, menuElementAttrs, menuHeight, menuWidth, menus, messages, offsetFn, onActiveChange, renderMenuOnClick, spritemap, trigger, triggerIcon, }: IProps): JSX.Element; displayName: string; }
    Parameters
    Properties

    active

    boolean | undefined

    Flag to indicate if the menu should be initially open (controlled).

    alignmentByViewport

    boolean | undefined

    Flag to align the DropDown menu within the viewport.

    alignmentPosition

    any

    Default position of menu element. Values come from ./Menu.

    containerElement

    React.JSXElementConstructor<any> | "div" | "li" | undefined

    HTML element tag that the container should render.

    defaultActive

    boolean | undefined

    Property to set the initial value of active (uncontrolled).

    defaultActiveMenu

    string | undefined

    The unique identifier of the menu that should be active on mount.

    initialActiveMenu

    string | undefined

    The unique identifier of the menu that should be active on mount.

    menuElementAttrs

    any

    Prop to pass DOM element attributes to DropDown.Menu.

    menuHeight

    "auto" | undefined

    menuWidth

    "sm" | "shrink" | "full" | undefined

    messages

    Messages | undefined= {"back":"","goTo":""}

    Messages for drilldown.

    offsetFn

    ((points: InternalDispatch) => [number, number]) | undefined

    Function for setting the offset of the menu from the trigger.

    onActiveChange

    any

    Callback the will be invoked when the active prop is changed (controlled).

    spritemap

    string | undefined

    Path to spritemap

    renderMenuOnClick

    boolean | undefined

    Flag indicating if the menu should be rendered lazily

    trigger *

    React.ReactElement<any, string | React.JSXElementConstructor<any>>

    Element that is used as the trigger which will activate the dropdown on click.

    triggerIcon

    string | null | undefined

    Flag indicating if the caret icon should be displayed on the right side.

    Returns
    Element