Dropdown
View .mdA dropdown lets people choose one or more options from a list. It can be used in forms where the options require custom styling [or as a component to filter or sort information].
Default
Loading interactive example. Full source is available below.
View example source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { useId } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
export const Default = () => {
const dropdownId = useId('dropdown-default');
const options = [
'Cat',
'Caterpillar',
'Corgi',
'Chupacabra',
'Dog',
'Ferret',
'Fish',
'Fox',
'Hamster',
'Snake',
];
const styles = /* css */ `
.dropdown-default-example {
display: grid;
grid-template-rows: repeat(1fr);
justify-items: start;
gap: var(--spacingVerticalXXS);
max-width: 400px;
}
`;
return (<>
<style>{styles}</style>
<div className="dropdown-default-example">
<label htmlFor={dropdownId}>Best pet</label>
<Dropdown id={dropdownId} placeholder="Select an animal">
{options.map((option) => (<Option key={option} disabled={option === 'Ferret'}>
{option}
</Option>))}
</Dropdown>
</div>
</>);
};
export default Default;
Resources
Best practices
Do
- Use placeholder text as an extra hint about the available options.
- Use the text prop to define a string value for complex options, especially options containing JSX.
- Set inlinePopup when possible for better VoiceOver support.
Don't
- Use placeholder text as the dropdown label.
- Repeat the same word or phrase at the beginning of every option.
Behavior and states
- If placeholder text is not set, dropdowns show as empty until a selection is made.
- Selecting a closed single-select field opens the list of options. Choosing an option closes the field.
- For multi-select interactions, each list item is accompanied by a checkbox.
- A multi-select list stays open until someone dismisses it by clicking off the list or pressing Esc.
- Put Dropdown in a Field if you need a label, info button, and validation.
API referenceGenerated CoreAI prop and type summaries
API
Generated CoreAI prop summaries, not replacement type declarations. Most native DOM/event and ARIA props are omitted. ? means optional; JSDoc shows defaults, deprecations, and conditional requirements. Defaults assume the standard CoreAI provider.
Dropdown API
A Dropdown component that extends Fluent UI's Dropdown.
interface DropdownProps {
/**
* Controls the colors and borders of the combobox trigger.
* @default 'outline'
*/
appearance?: "outline" | "filled-darker" | "filled-lighter" | "underline";
as?: "button";
/**
* The primary slot, the element with role="combobox"
*/
button?: NonNullable<WithSlotShorthandValue<{ as?: "button"; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null; }, "children"> & { children?: any; }> | null> | undefined;
children?: any;
className?: string;
/**
* If set, the combobox will show an icon to clear the current value.
*/
clearable?: boolean;
/**
* The dropdown clear icon
*/
clearButton?: WithSlotShorthandValue<{ as?: "button"; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & { ref?: ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null; }, "children"> & { children?: any; }> | null | undefined;
/**
* The default open state when open is uncontrolled
*/
defaultOpen?: boolean;
/**
* For an uncontrolled component, sets the initial selection.
* If this is set, the `defaultValue` prop MUST also be set.
*/
defaultSelectedOptions?: string[];
/**
* The default value displayed in the trigger input or button when the combobox's value is uncontrolled
*/
defaultValue?: string | (readonly string[] & string);
/**
* Disable auto-focusing on the first item when mounting.
* @default false
*/
disableAutoFocus?: boolean;
/**
* Collapse the control to its content width when nothing is selected and
* grow it with the typed/selected content. Only available when `label` is set.
*/
dynamicWidth?: boolean;
/**
* The dropdown arrow icon
*/
expandIcon?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;
/**
* Render the combobox's popup inline in the DOM.
* This has accessibility benefits, particularly for touch screen readers.
*/
inlinePopup?: boolean;
/**
* Optional inline label rendered inside the control, before the value.
*/
label?: string;
/**
* The dropdown listbox slot
*/
listbox?: WithSlotShorthandValue<Omit<ListboxSlots, "root"> & Omit<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; }, "ref"> & SelectionProps & { disableAutoFocus?: boolean | undefined; } & RefAttributes<HTMLDivElement>> | null | undefined;
/**
* Where the portal children are mounted on DOM
* @default a new element on document.body without any styling
*/
mountNode?: HTMLElement | { element?: HTMLElement | null; className?: string; } | null | undefined;
/**
* Sets the selection type to multiselect.
* Set this to true for multiselect, even if fully controlling selection state.
* This enables styles and accessibility properties to be set.
* @default false
*/
multiselect?: boolean;
onActiveOptionChange?: EventHandler<ActiveOptionChangeData>;
/**
* Callback when the open/closed state of the dropdown changes
*/
onOpenChange?: ((e: ComboboxBaseOpenEvents, data: ComboboxBaseOpenChangeData) => void);
/**
* Callback when an option is selected
*/
onOptionSelect?: ((event: SelectionEvents, data: OptionOnSelectData) => void);
/**
* Sets the open/closed state of the dropdown.
* Use together with onOpenChange to fully control the dropdown's visibility
*/
open?: boolean;
/**
* If set, the placeholder will show when no value is selected
*/
placeholder?: string;
/**
* Configure the positioning of the combobox dropdown.
* Please refer to the [positioning documentation](https://react.fluentui.dev/?path=/docs/concepts-developer-positioning-components--default#anchor-to-target)
* for more details.
*/
positioning?: PositioningShorthand;
ref?: Ref<HTMLButtonElement>;
/**
* The root dropdown slot
*/
root?: NonNullable<WithSlotShorthandValue<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; }> | null> | undefined;
/**
* An array of selected option keys.
* Use this with `onOptionSelect` to directly control the selected option(s)
* If this is set, the `value` prop MUST also be controlled.
*/
selectedOptions?: string[];
/**
* Controls the size of the combobox faceplate
* @default 'medium'
*/
size?: "small" | "medium" | "large";
style?: CSSProperties;
/**
* Controls text overflow behavior for the value. When true, long text is truncated with an ellipsis.
*/
truncateTextOverflow?: boolean;
/**
* The value displayed by the Combobox.
* Use this with `onOptionSelect` to directly control the displayed value string
*/
value?: string | (readonly string[] & string);
}
Listbox API
Listbox component: a standalone selection control, or the popup in a Combobox
interface ListboxProps {
as?: "div";
children?: any;
className?: string;
/**
* For an uncontrolled component, sets the initial selection.
* If this is set, the `defaultValue` prop MUST also be set.
*/
defaultSelectedOptions?: string[];
/**
* Disable auto-focusing on the first item when mounting.
* @default false
*/
disableAutoFocus?: boolean;
/**
* Sets the selection type to multiselect.
* Set this to true for multiselect, even if fully controlling selection state.
* This enables styles and accessibility properties to be set.
* @default false
*/
multiselect?: boolean;
/**
* Callback when an option is selected
*/
onOptionSelect?: ((event: SelectionEvents, data: OptionOnSelectData) => void);
ref?: Ref<HTMLDivElement>;
/**
* An array of selected option keys.
* Use this with `onOptionSelect` to directly control the selected option(s)
* If this is set, the `value` prop MUST also be controlled.
*/
selectedOptions?: string[];
style?: CSSProperties;
}
Option API
Option component: a styled child option of a Combobox
interface OptionProps {
as?: "div";
/**
* The check icon that is visible for selected options
*/
checkIcon?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;
children?: any;
className?: string;
/**
* Sets an option to the `disabled` state.
* Disabled options cannot be selected, but are still keyboard navigable
*/
disabled?: boolean;
ref?: Ref<HTMLDivElement>;
style?: CSSProperties;
/**
* An optional override the string value of the Option's display text,
* defaulting to the Option's child content.
* This is used as the Dropdown button's or Combobox input's value when the option is selected,
* and as the comparison for type-to-find keyboard functionality.
* The string value of the Option's display text when the Option's children are not a string.
* This is used as the Dropdown button's or Combobox input's value when the option is selected,
* and as the comparison for type-to-find keyboard functionality.
*/
text?: string;
/**
* Defines a unique identifier for the option.
* Use this to control selectedOptions, or to get the option value in the onOptionSelect callback.
* Defaults to `text` if not provided.
*/
value?: string;
}
Implementation
CoreAI 0.12.0; Fluent 9.74.9.
Import components from @coreai-ds/react-components. CoreAI guidance above takes precedence over API demonstration examples.
Active Option Change
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import { Persona } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const ActiveOptionChange = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown');
const styles = useStyles();
const [activeOptionText, setActiveOptionText] = React.useState('');
const onActiveOptionChange = React.useCallback<NonNullable<DropdownProps['onActiveOptionChange']>>((_, data) => {
if (data?.nextOption?.text) {
setActiveOptionText(data?.nextOption?.text);
}
}, [setActiveOptionText]);
const onMouseEnter = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
setActiveOptionText(`${e.currentTarget.textContent} (Mouse enter)`);
}, [setActiveOptionText]);
return (<div className={styles.root}>
{activeOptionText}
<label htmlFor={dropdownId}>Schedule a meeting</label>
<Dropdown id={dropdownId} onActiveOptionChange={onActiveOptionChange} {...props}>
<Option text="Katri Athokas" onMouseEnter={onMouseEnter}>
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Katri Athokas" presence={{
status: 'available',
}} secondaryText="Available"/>
</Option>
<Option text="Elvia Atkins" onMouseEnter={onMouseEnter}>
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Elvia Atkins" presence={{
status: 'busy',
}} secondaryText="Busy"/>
</Option>
<Option text="Cameron Evans" onMouseEnter={onMouseEnter}>
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Cameron Evans" presence={{
status: 'away',
}} secondaryText="Away"/>
</Option>
<Option text="Wanda Howard" onMouseEnter={onMouseEnter}>
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Wanda Howard" presence={{
status: 'out-of-office',
}} secondaryText="Out of office"/>
</Option>
</Dropdown>
</div>);
};
export default ActiveOptionChange;
Appearance
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '20px',
maxWidth: '400px',
'> div': {
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
// need padding to see the background color for filled variants
padding: '5px 20px 10px',
},
},
// filledLighter and filledDarker appearances depend on particular background colors
filledLighter: {
backgroundColor: tokens.colorNeutralBackgroundInverted,
'> label': {
color: tokens.colorNeutralForegroundInverted2,
},
'> h3': {
color: tokens.colorNeutralForegroundInverted2,
},
},
filledDarker: {
backgroundColor: tokens.colorNeutralBackgroundInverted,
'> label': {
color: tokens.colorNeutralForegroundInverted2,
},
'> h3': {
color: tokens.colorNeutralForegroundInverted2,
},
},
});
export const Appearance = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown');
const styles = useStyles();
return (<div className={styles.root}>
<div>
<h3>Outline</h3>
<label htmlFor={`${dropdownId}-outline`}>Select an animal</label>
<Dropdown id={`${dropdownId}-outline`} placeholder="-" appearance="outline" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
<div>
<h3>Underline</h3>
<label htmlFor={`${dropdownId}-underline`}>Select an animal</label>
<Dropdown id={`${dropdownId}-underline`} placeholder="-" appearance="underline" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
<div className={styles.filledDarker}>
<h3>Filled Darker</h3>
<label htmlFor={`${dropdownId}-filledDarker`}>Select an animal</label>
<Dropdown id={`${dropdownId}-filledDarker`} placeholder="-" appearance="filled-darker" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
<div className={styles.filledLighter}>
<h3>Filled Lighter</h3>
<label htmlFor={`${dropdownId}-filledLighter`}>Select an animal</label>
<Dropdown id={`${dropdownId}-filledLighter`} placeholder="-" appearance="filled-lighter" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
</div>);
};
export default Appearance;
Clearable
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const Clearable = (): JSXElement => {
const dropdownId = useId('');
const styles = useStyles();
return (<div className={styles.root}>
<Label htmlFor={dropdownId}>Pick a color</Label>
<Dropdown clearable id={dropdownId} placeholder="Select a color">
<Option>Red</Option>
<Option>Green</Option>
<Option>Blue</Option>
</Dropdown>
</div>);
};
export default Clearable;
Complex Options
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import { Persona } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const ComplexOptions = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown');
const styles = useStyles();
return (<div className={styles.root}>
<label htmlFor={dropdownId}>Schedule a meeting</label>
<Dropdown id={dropdownId} {...props}>
<Option text="Katri Athokas">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Katri Athokas" presence={{
status: 'available',
}} secondaryText="Available"/>
</Option>
<Option text="Elvia Atkins">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Elvia Atkins" presence={{
status: 'busy',
}} secondaryText="Busy"/>
</Option>
<Option text="Cameron Evans">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Cameron Evans" presence={{
status: 'away',
}} secondaryText="Away"/>
</Option>
<Option text="Wanda Howard">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Wanda Howard" presence={{
status: 'out-of-office',
}} secondaryText="Out of office"/>
</Option>
</Dropdown>
</div>);
};
export default ComplexOptions;
Controlled
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import { Persona } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
justifyItems: 'start',
gap: '20px',
maxWidth: '400px',
},
field: {
display: 'grid',
justifyItems: 'start',
gap: '2px',
},
});
export const Controlled = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const comboId = useId('combo-controlled');
const styles = useStyles();
const [selectedOptions, setSelectedOptions] = React.useState<string[]>(['eatkins']);
const [value, setValue] = React.useState('Elvia Atkins');
const onOptionSelect: (typeof props)['onOptionSelect'] = (ev, data) => {
setSelectedOptions(data.selectedOptions);
setValue(data.optionText ?? '');
};
return (<div className={styles.root}>
<div className={styles.field}>
<label htmlFor={`${comboId}-default`}>Schedule a meeting (default selection)</label>
<Dropdown id={`${comboId}-default`} {...props} defaultValue="Elvia Atkins" defaultSelectedOptions={['eatkins']}>
<Option text="Katri Athokas" value="kathok">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Katri Athokas" presence={{
status: 'available',
}} secondaryText="Available"/>
</Option>
<Option text="Elvia Atkins" value="eatkins">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Elvia Atkins" presence={{
status: 'busy',
}} secondaryText="Busy"/>
</Option>
<Option text="Cameron Evans" value="cevans">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Cameron Evans" presence={{
status: 'away',
}} secondaryText="Away"/>
</Option>
<Option text="Wanda Howard" value="whoward">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Wanda Howard" presence={{
status: 'out-of-office',
}} secondaryText="Out of office"/>
</Option>
</Dropdown>
</div>
<div className={styles.field}>
<label htmlFor={`${comboId}-controlled`}>Schedule a meeting (controlled selection)</label>
<Dropdown id={`${comboId}-controlled`} {...props} value={value} selectedOptions={selectedOptions} onOptionSelect={onOptionSelect}>
<Option text="Katri Athokas" value="kathok">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Katri Athokas" presence={{
status: 'available',
}} secondaryText="Available"/>
</Option>
<Option text="Elvia Atkins" value="eatkins">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Elvia Atkins" presence={{
status: 'busy',
}} secondaryText="Busy"/>
</Option>
<Option text="Cameron Evans" value="cevans">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Cameron Evans" presence={{
status: 'away',
}} secondaryText="Away"/>
</Option>
<Option text="Wanda Howard" value="whoward">
<Persona avatar={{ color: 'colorful', 'aria-hidden': true }} name="Wanda Howard" presence={{
status: 'out-of-office',
}} secondaryText="Out of office"/>
</Option>
</Dropdown>
</div>
</div>);
};
export default Controlled;
Controlling Open And Close
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DropdownProps } from "@coreai-ds/react-components";
import type { CheckboxProps } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { Checkbox } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const ControllingOpenAndClose = (): JSXElement => {
const styles = useStyles();
const [open, setOpen] = React.useState(false);
const handleOpenChange: DropdownProps['onOpenChange'] = (e, data) => setOpen(data.open);
const onChange: CheckboxProps['onChange'] = (e, data) => setOpen(!!data.checked);
const dropdownId = useId('dropdown');
const options = ['Cat', 'Caterpillar', 'Corgi', 'Chupacabra', 'Dog', 'Ferret', 'Fish', 'Fox', 'Hamster', 'Snake'];
return (<div className={styles.root}>
<Checkbox value="open" name="state" label="open" checked={open} onChange={onChange}/>
<label htmlFor={dropdownId}>Best pet</label>
<Dropdown id={dropdownId} placeholder="Select an animal" open={open} onOpenChange={handleOpenChange}>
{options.map(option => (<Option key={option} disabled={option === 'Ferret'}>
{option}
</Option>))}
</Dropdown>
</div>);
};
export default ControllingOpenAndClose;
Custom Options
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { AnimalCat24Filled } from "@fluentui/react-icons";
import { AnimalDog24Filled } from "@fluentui/react-icons";
import { AnimalRabbit24Filled } from "@fluentui/react-icons";
import { AnimalTurtle24Filled } from "@fluentui/react-icons";
import { FoodFish24Filled } from "@fluentui/react-icons";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import type { OptionGroupProps } from "@coreai-ds/react-components";
import { OptionGroup } from "@coreai-ds/react-components";
import type { OptionProps } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import { CheckboxChecked24Regular } from "@fluentui/react-icons";
import * as React from 'react';
const animalIcons = {
Cat: AnimalCat24Filled,
Dog: AnimalDog24Filled,
Rabbit: AnimalRabbit24Filled,
Turtle: AnimalTurtle24Filled,
Fish: FoodFish24Filled,
};
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
listbox: {
maxHeight: '200px',
},
});
type CustomOptionProps = Partial<OptionProps> & {
animal: keyof typeof animalIcons;
};
const useCustomOptionStyles = makeStyles({
option: {
display: 'flex',
alignItems: 'center',
gap: '5px',
},
icon: { flex: '0 0 auto' },
text: { flex: '1 1 auto' },
});
const CustomOption = (props: CustomOptionProps) => {
const { animal, ...optionProps } = props;
const Icon = animalIcons[animal];
const styles = useCustomOptionStyles();
return (<Option text={animal} className={styles.option} checkIcon={<CheckboxChecked24Regular />} {...optionProps}>
<Icon className={styles.icon}/>
<span className={styles.text}>{animal}</span>
</Option>);
};
const CustomOptionGroup = (props: Partial<OptionGroupProps> & {
options: (keyof typeof animalIcons)[];
}) => {
const { label, options, ...optionGroupProps } = props;
const labelSlot = typeof label === 'object' ? label : { children: label };
return (<OptionGroup label={{ style: { fontStyle: 'italic' }, ...labelSlot }} {...optionGroupProps}>
{options.map(option => (<CustomOption key={option} animal={option}/>))}
</OptionGroup>);
};
export const CustomOptions = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown');
const land = ['Cat', 'Dog', 'Rabbit'] as (keyof typeof animalIcons)[];
const water = ['Fish', 'Turtle'] as (keyof typeof animalIcons)[];
const styles = useStyles();
return (<div className={styles.root}>
<label htmlFor={dropdownId}>Best pet</label>
<Dropdown id={dropdownId} listbox={{ className: styles.listbox }} placeholder="Select an animal" {...props}>
<CustomOptionGroup label="Land" options={land}/>
<CustomOptionGroup label="Sea" options={water}/>
</Dropdown>
</div>);
};
export default CustomOptions;
Disabled
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const Disabled = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const comboId = useId('combo-disabled');
const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake'];
const styles = useStyles();
return (<div className={styles.root}>
<label htmlFor={comboId}>Best pet</label>
<Dropdown id={comboId} disabled placeholder="Select an animal" {...props}>
{options.map(option => (<Option key={option}>{option}</Option>))}
</Dropdown>
</div>);
};
export default Disabled;
Grouped
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { OptionGroup } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const Grouped = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown-grouped');
const land = ['Cat', 'Dog', 'Ferret', 'Hamster'];
const water = ['Fish', 'Jellyfish', 'Octopus', 'Seal'];
const styles = useStyles();
return (<div className={styles.root}>
<label htmlFor={dropdownId}>Best pet</label>
<Dropdown id={dropdownId} placeholder="Select an animal" {...props}>
<OptionGroup label="Land">
{land.map(option => (<Option key={option} disabled={option === 'Ferret'}>
{option}
</Option>))}
</OptionGroup>
<OptionGroup label="Sea">
{water.map(option => (<Option key={option}>{option}</Option>))}
</OptionGroup>
</Dropdown>
</div>);
};
export default Grouped;
Inline Label
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import * as React from 'react';
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
export const InlineLabel = () => {
const statusOptions = ['Not started', 'In progress', 'Blocked', 'Completed'];
const [status, setStatus] = React.useState<string>('');
const [dynamicStatus, setDynamicStatus] = React.useState<string>('');
const styles = /* css */ `
.dropdown-inline-label-example {
display: flex;
flex-direction: column;
align-items: flex-start;
row-gap: var(--spacingVerticalL);
max-width: 320px;
padding: var(--spacingVerticalL);
}
.dropdown-inline-label-example__field {
display: flex;
flex-direction: column;
align-items: flex-start;
row-gap: var(--spacingVerticalXS);
}
.dropdown-inline-label-example__prop-label {
font-family: var(--fontFamilyMonospace);
font-size: var(--fontSizeBase200);
color: var(--colorNeutralForeground3);
}
`;
return (<>
<style>{styles}</style>
<div className="dropdown-inline-label-example">
<div className="dropdown-inline-label-example__field">
<code className="dropdown-inline-label-example__prop-label">label</code>
<Dropdown label="Status" placeholder="Select a status" value={status} selectedOptions={status ? [status] : []} clearable onOptionSelect={(_event, data) => setStatus(data.optionValue ?? '')}>
{statusOptions.map((option) => (<Option key={option}>{option}</Option>))}
</Dropdown>
</div>
<div className="dropdown-inline-label-example__field">
<code className="dropdown-inline-label-example__prop-label">label, dynamicWidth</code>
<Dropdown label="Status" dynamicWidth value={dynamicStatus} selectedOptions={dynamicStatus ? [dynamicStatus] : []} clearable onOptionSelect={(_event, data) => setDynamicStatus(data.optionValue ?? '')}>
{statusOptions.map((option) => (<Option key={option}>{option}</Option>))}
</Dropdown>
</div>
</div>
</>);
};
export default InlineLabel;
Multiselect
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '400px',
},
});
export const Multiselect = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const comboId = useId('combo-multi');
const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake'];
const styles = useStyles();
return (<div className={styles.root}>
<label htmlFor={comboId}>Best pet</label>
<Dropdown id={comboId} multiselect={true} placeholder="Select an animal" {...props}>
{options.map(option => (<Option key={option} disabled={option === 'Ferret'}>
{option}
</Option>))}
</Dropdown>
</div>);
};
export default Multiselect;
Size
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '20px',
maxWidth: '400px',
'> div': {
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
},
},
});
export const Size = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const comboId = useId('combobox');
const styles = useStyles();
return (<div className={styles.root}>
<div>
<h3>Small</h3>
<label htmlFor={`${comboId}-small`}>Best pet</label>
<Dropdown id={`${comboId}-small`} placeholder="Select an animal" size="small" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
<div>
<h3>Medium</h3>
<label htmlFor={`${comboId}-med`}>Best pet</label>
<Dropdown id={`${comboId}-med`} placeholder="Select an animal" size="medium" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
<div>
<h3>Large</h3>
<label htmlFor={`${comboId}-large`}>Best pet</label>
<Dropdown id={`${comboId}-large`} placeholder="Select an animal" size="large" {...props}>
<Option>Cat</Option>
<Option>Dog</Option>
<Option>Bird</Option>
</Dropdown>
</div>
</div>);
};
export default Size;
Truncate
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import * as React from 'react';
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
export const Truncate = () => {
const longOptions = [
'Awaiting stakeholder review and final sign-off',
'Blocked by an upstream dependency in another team',
'In progress — implementation roughly halfway complete',
'Completed and deployed to the production environment',
];
const [status, setStatus] = React.useState<string>(longOptions[0]);
const styles = /* css */ `
.dropdown-truncate-example {
display: flex;
flex-direction: column;
align-items: flex-start;
row-gap: var(--spacingVerticalL);
max-width: 320px;
padding: var(--spacingVerticalL);
}
.dropdown-truncate-example__field {
display: flex;
flex-direction: column;
align-items: flex-start;
row-gap: var(--spacingVerticalXS);
}
.dropdown-truncate-example__prop-label {
font-family: var(--fontFamilyMonospace);
font-size: var(--fontSizeBase200);
color: var(--colorNeutralForeground3);
}
.dropdown-truncate-example__dropdown--narrow {
width: 220px;
}
.dropdown-truncate-example__dropdown--narrower {
width: 160px;
}
`;
return (<>
<style>{styles}</style>
<div className="dropdown-truncate-example">
<div className="dropdown-truncate-example__field">
<code className="dropdown-truncate-example__prop-label">truncateTextOverflow</code>
<Dropdown className="dropdown-truncate-example__dropdown--narrow" aria-label="Status" placeholder="Select a status" truncateTextOverflow value={status} selectedOptions={status ? [status] : []} clearable onOptionSelect={(_event, data) => setStatus(data.optionValue ?? '')}>
{longOptions.map((option) => (<Option key={option}>{option}</Option>))}
</Dropdown>
</div>
<div className="dropdown-truncate-example__field">
<code className="dropdown-truncate-example__prop-label">truncateTextOverflow</code>
<Dropdown className="dropdown-truncate-example__dropdown--narrower" aria-label="Status" placeholder="Select a status" truncateTextOverflow value={status} selectedOptions={status ? [status] : []} clearable onOptionSelect={(_event, data) => setStatus(data.optionValue ?? '')}>
{longOptions.map((option) => (<Option key={option}>{option}</Option>))}
</Dropdown>
</div>
</div>
</>);
};
export default Truncate;
Truncated Value
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
// Stack the label above the field with a gap
display: 'grid',
gridTemplateRows: 'repeat(1fr)',
justifyItems: 'start',
gap: '2px',
maxWidth: '200px',
},
listbox: {
maxHeight: '200px',
},
// these styles wrap the value text within the dropdown button and cause it to truncate
truncatedText: {
overflowX: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
// these styles allow option text to break and wrap on long words, e.g. emails
optionText: {
overflow: 'hidden',
overflowWrap: 'break-word',
},
});
export const TruncatedValue = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const dropdownId = useId('dropdown');
const options = [
'Cat',
'Caterpillar',
'Corgi',
'Chupacabra',
'Dog',
'Ferret',
'Fish',
'Fox',
'Hamster',
'Snake',
'SuperLongName_123456789_SomeMoreStuffToMakeItLonger@fluentui.dev',
'Screaming hairy armadillo (Chaetophractus vellerosus)',
];
const styles = useStyles();
const placeholder = 'Select an animal';
// show truncated option by default
const defaultValue = options[11];
const [value, setValue] = React.useState(defaultValue);
return (<div className={styles.root}>
<label htmlFor={dropdownId}>Best pet</label>
<Dropdown id={dropdownId} listbox={{ className: styles.listbox }} button={<span className={styles.truncatedText}>{value}</span>} onOptionSelect={(e, data) => setValue(data.optionText ?? placeholder)} defaultSelectedOptions={[defaultValue]} defaultValue={defaultValue} {...props}>
{options.map(option => (<Option key={option} text={option} disabled={option === 'Ferret'}>
<span className={styles.optionText}>{option}</span>
</Option>))}
</Dropdown>
</div>);
};
export default TruncatedValue;
With Field
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DropdownProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { Dropdown } from "@coreai-ds/react-components";
import { Option } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
maxWidth: '400px',
},
});
export const WithField = (props: Partial<Omit<DropdownProps, "label" | "dynamicWidth">>): JSXElement => {
const styles = useStyles();
const options = ['Cat', 'Caterpillar', 'Corgi', 'Chupacabra', 'Dog', 'Ferret', 'Fish', 'Fox', 'Hamster', 'Snake'];
return (<Field label="Best pet" required hint="Try picking 'Cat'" className={styles.root}>
<Dropdown placeholder="Select an animal" {...props}>
{options.map(option => (<Option key={option} disabled={option === 'Ferret'}>
{option}
</Option>))}
</Dropdown>
</Field>);
};
export default WithField;