Picker

A select is very similar to a dropdown visually but it let users select options from the panel and then represent the selection in the button.

installyarn add @clayui/core
versionNPM Version
useimport {Option, Picker} from '@clayui/core';

Example

import {Provider, Option, Picker} from '@clayui/core';
import Form from '@clayui/form';
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">
				<div style={{width: '100px'}}>
					<Form.Group>
						<label htmlFor="picker" id="picker-label">
							Year
						</label>
						<Picker
							width={85}
							aria-labelledby="picker-label"
							id="picker"
							items={[
								'2020',
								'2021',
								'2022',
								'2023',
								'2024',
								'2025',
								'2026',
								'2027',
								'2028',
							]}
							placeholder="Year"
						>
							{(item) => <Option key={item}>{item}</Option>}
						</Picker>
					</Form.Group>
				</div>
			</div>
		</Provider>
	);
}

Introduction

The Picker component is visually similar to a DropDown component but developed specifically for the user to select options from the menu but following the w3c accessibility recommendations.

Like the TreeView, VerticalBar and other components this is a middle-level component rather than a low-level or high-level component. Unlike a DropDown, the Picker has simplified APIs and OOTB features to follow accessibility patterns for a combobox-only but following the same APIs pattern and allowing flexibility to customize the content of the options and the trigger.

Content

Content rendered in the <Picker /> Menu can be done in two different ways, static and dynamic content just like in DropDown, the choice depends on the use case.

Static

Static content is when the <Picker /> options do not change during the lifecycle of the application or are hardcoded options.

<Form.Group>
	<label htmlFor="picker" id="picker-label">
		Choose a fruit
	</label>
	<Picker aria-labelledby="picker-label" id="picker">
		<Option key="apple">Apple</Option>
		<Option disabled key="banana">
			Banana
		</Option>
		<Option key="mangos">Mangos</Option>
		<Option key="blueberry">Blueberry</Option>
	</Picker>
</Form.Group>

Dynamic

Unlike static content, dynamic content is when the options can change during the lifecycle of the application or when the data comes from a service. Dynamic content rendering is data agnostic, this allows you to configure how to render the component options regardless of the chosen data structure.

<Form.Group>
	<label htmlFor="picker" id="picker-label">
		Choose a fruit
	</label>
	<Picker
		aria-labelledby="picker-label"
		id="picker"
		items={['Apple', 'Banana', 'Mangos']}
	>
		{(item) => <Option key={item}>{item}</Option>}
	</Picker>
</Form.Group>

Controlled and Uncontrolled component

As with any component in Clay, controlled and uncontrolled components are the ability to manage state in the parent or let Clay control the state of the component. You can read more about this in our blog.

For the <Picker /> component you can control the selectedKey and active states by adding a state to your component, this is only advisable when you need this information otherwise don’t use it.

If you just need to set the initial state of the selectedKey, use the defaultSelectedKey property which is appropriate for that use case.

Info The selectedKey property is represented by the key property configured in the Option component, so the component can identify which value is selected and which should be shown in the trigger.


When the content rendering is dynamic and the data has id property defined, the component uses id instead of key.

function Example() {
	// Controlled
	const [active, setActive] = useState(false);

	// Controlled
	const [selectedKey, setSelectedKey] = useState('');

	return (
		<Form.Group>
			<label htmlFor="picker" id="picker-label">
				Choose a fruit
			</label>
			<Picker
				active={active}
				aria-labelledby="picker-label"
				id="picker"
				onActiveChange={setActive}
				onSelectionChange={setSelectedKey}
				selectedKey={selectedKey}
			>
				<Option key="apple">Apple</Option>
				<Option key="banana">Banana</Option>
				<Option key="mangos">Mangos</Option>
			</Picker>
		</Form.Group>
	);
}

Composition

The composition allows you to customize the component or add new features. See some examples:

Custom Trigger

import {Provider, Option, Picker, Icon} from '@clayui/core';
import {useId} from '@clayui/shared';
import Form from '@clayui/form';
import React from 'react';

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

const Trigger = React.forwardRef(({children, ...otherProps}, ref) => (
	<div ref={ref} {...otherProps} tabIndex={0}>
		<Icon className="mr-2" symbol="user" />
		{children}
	</div>
));

export default function App() {
	const pickerId = useId();
	const labelId = useId();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<div style={{width: '150px'}}>
					<Form.Group>
						<label htmlFor={pickerId} id={labelId}>
							Choose a user
						</label>
						<Picker
							aria-labelledby={labelId}
							as={Trigger}
							id={pickerId}
							items={['Liam', 'Noah', 'Oliver']}
						>
							{(item) => <Option key={item}>{item}</Option>}
						</Picker>
					</Form.Group>
				</div>
			</div>
		</Provider>
	);
}

Custom Options

import {Provider, Option, Picker, Text} from '@clayui/core';
import {useId} from '@clayui/shared';
import Layout from '@clayui/layout';
import Label from '@clayui/label';
import Form from '@clayui/form';
import React from 'react';

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

export default function App() {
	const pickerId = useId();
	const labelId = useId();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<div style={{width: '150px'}}>
					<Form.Group>
						<label htmlFor={pickerId} id={labelId}>
							Choose a user
						</label>
						<Picker
							aria-labelledby={labelId}
							id={pickerId}
							items={['Liam', 'Noah', 'Oliver']}
						>
							{(item) => (
								<Option key={item} textValue={item}>
									<Layout.ContentRow>
										<Layout.ContentCol expand>
											<Text size={3} weight="semi-bold">
												{item}
											</Text>
											<Text
												aria-hidden
												color="secondary"
												size={2}
											>
												Description
											</Text>
										</Layout.ContentCol>
										<Layout.ContentCol>
											<Label
												aria-hidden
												displayType="success"
											>
												Active
											</Label>
										</Layout.ContentCol>
									</Layout.ContentRow>
								</Option>
							)}
						</Picker>
					</Form.Group>
				</div>
			</div>
		</Provider>
	);
}

Group

import {Provider, Option, Picker} from '@clayui/core';
import {useId} from '@clayui/shared';
import DropDown from '@clayui/drop-down';
import Form from '@clayui/form';
import React from 'react';

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

export default function App() {
	const pickerId = useId();
	const labelId = useId();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4">
				<div style={{width: '150px'}}>
					<Form.Group>
						<label htmlFor={pickerId} id={labelId}>
							Select an option
						</label>
						<Picker
							aria-labelledby={labelId}
							id={pickerId}
							items={[
								{
									items: [
										{label: 'Apple', value: '1'},
										{label: 'Banana', value: '2'},
										{label: 'Mangos', value: '3'},
									],
									label: 'Fruit',
								},
								{
									items: [
										{label: 'Onions', value: '4'},
										{label: 'abc', value: '5'},
										{label: 'def', value: '6'},
									],
									label: 'Vegetable',
								},
							]}
						>
							{(group) => (
								<DropDown.Group
									header={group.label}
									items={group.items}
								>
									{(item) => (
										<Option key={item.value}>
											{item.label}
										</Option>
									)}
								</DropDown.Group>
							)}
						</Picker>
					</Form.Group>
				</div>
			</div>
		</Provider>
	);
}

Flexible width

import {Provider, Option, Picker} from '@clayui/core';
import Form from '@clayui/form';
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">
				<div style={{width: '100px'}}>
					<Form.Group>
						<label htmlFor="picker" id="picker-label">
							Year
						</label>
						<Picker
							width={85}
							aria-labelledby="picker-label"
							id="picker"
							items={[
								'2020',
								'2021',
								'2022',
								'2023',
								'2024',
								'2025',
								'2026',
								'2027',
								'2028',
							]}
							placeholder="Year"
						>
							{(item) => <Option key={item}>{item}</Option>}
						</Picker>
					</Form.Group>
				</div>
			</div>
		</Provider>
	);
}

Hybrid component

Native select for mobile devices offers a better experience compared to Picker in some cases. The Picker offers the possibility to render using the native selector of the browser of the device when it is detected that it is on a mobile device, by default this property is disabled but it can be enabled by setting the native property to true.

Warning If the content of the Option component is not simple text, for it to work correctly set the property textValue to ensure that the component knows what the option label is.

<Form.Group>
	<label htmlFor="picker" id="picker-label">
		Choose a fruit
	</label>
	<Picker aria-labelledby="picker-label" id="picker" native>
		<Option key="apple">Apple</Option>
		<Option key="banana">Banana</Option>
		<Option key="mangos">Mangos</Option>
	</Picker>
</Form.Group>

API Reference

Picker

<T extends Record<string, any> | string | number>({ UNSAFE_behavior, UNSAFE_menuClassName, active: externalActive, as: As, children, className, defaultActive, defaultSelectedKey, direction, disabled, id, items, messages, native, onActiveChange, onSelectionChange, placeholder, selectedKey: externalSelectedKey, width, ...otherProps }: Props<T>) => JSX.Element
Parameters
Properties

active

boolean | undefined

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

aria-describedby

string | undefined

The global aria-describedby attribute identifies the element that describes the component.

aria-label

string | undefined

The aria-label attribute defines a string value that labels an interactive element.

aria-labelledby

string | undefined

The aria-labelledby attribute identifies the element (or elements) that labels the element it is applied to.

as

"button" | React.ForwardRefExoticComponent<any> | ((props: React.HTMLAttributes<HTMLElement>) => JSX.Element) | undefined= "button"

Custom trigger component.

className

string | undefined

Sets the CSS className for the component.

defaultActive

boolean | undefined

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

defaultSelectedKey

React.Key | undefined

The initial selected key (uncontrolled).

direction

"bottom" | "top" | undefined= "bottom"

Direction the menu will render relative to the Picker.

disabled

boolean | undefined

Flag to indicate that the component is disabled.

id

string | undefined

The id of the component.

messages

{ itemSelected: string; itemDescribedby: string; } | undefined= {"itemDescribedby":"You are currently on a text element, inside of a list box.","itemSelected":"{0}, selected"}

Texts used for assertive messages to SRs.

native

boolean | undefined

Flag to make the component hybrid, when identified it is on a mobile device it will use the native selector.

onActiveChange

any

Callback for when the active state changes (controlled).

onSelectionChange

any

Callback calling when an option is selected.

placeholder

string | undefined= "Select an option"

Text that appears when you don't have an item selected.

selectedKey

React.Key | undefined

The currently selected key (controlled).

width

number | undefined

Sets the width of the panel.

UNSAFE_menuClassName

string | undefined

Sets the className for the React.Portal Menu element.

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.

Returns
Element

Option

typeof Option
Parameters
Properties

aria-describedby

string | undefined

The global aria-describedby attribute identifies the element (or elements) that describes the element on which the attribute is set.

aria-label

string | undefined

The aria-label attribute defines a string value that labels an interactive element.

aria-labelledby

string | undefined

The aria-labelledby attribute identifies the element (or elements) that labels the element it is applied to.

aria-posinset

number | undefined

Sets the number or position in the current set of listitems or treeitems when not all items are present in the DOM.

aria-setsize

number | undefined

Sets the number of items in the current set of listitems or treeitems when not all items in the set are present in the DOM.

children

React.ReactNode

The contents of the component.

disabled

boolean | undefined

Flag that indicates if option is disabled.

href

string | undefined

Path or URL

textValue

string | undefined

Sets a text value if the component's content is not plain text. This value is used in the combobox element to show the selected option.

Returns
Element