Default

Loading interactive example. Full source is available below.

View example source
Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const Default = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter((option) => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map((option) => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map((option) => (<TagPickerOption media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default Default;

Resources

Behavior and states

  • Focusing the tag picker invokes a dropdown of available items.
  • Selecting a dropdown item closes the dropdown and inserts a tag into the input.
  • Typing in the input filters the list, with the closest match active first.
  • Pressing Enter selects the first item unless arrow keys move the active descendant.
  • The dropdown can show system-provided items that represent files, people, or meetings.
  • Arrow keys move through the filtered list.
  • Inserted tags wrap by default and increase the input height.
  • Pressing Backspace while the input is focused removes the last tag.
  • A tag picker provides a secondary action slot for extra functionality when needed, such as clearing all tags.
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.

TagPicker API

interface TagPickerProps {
  /**
   * Controls the colors and borders of the combobox trigger.
   * @default 'filled-darker'
   */
  appearance?: "outline" | "filled-darker" | "filled-lighter" | "underline";

  /**
   * Can contain two children including a trigger and a popover
   */
  children: JSXElement | [JSXElement, false | JSXElement | 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[];

  /**
   * Disable auto-focusing on the first item when mounting.
   * @default false
   */
  disableAutoFocus?: boolean;

  /**
   * TagPickers are rendered out of DOM order on `document.body` by default,
   * use this to render the popover in DOM order
   * @default false
   */
  inline?: boolean;

  /**
   * By default, when a single children is provided, the TagPicker will assume that the children
   * is a popover. By setting this prop to true, the children will be treated as a trigger instead.
   * @default false
   */
  noPopover?: boolean;

  onOpenChange?: EventHandler<TagPickerOnOpenChangeData>;

  onOptionSelect?: EventHandler<TagPickerOnOptionSelectData>;

  /**
   * Sets the open/closed state of the dropdown.
   * Use together with onOpenChange to fully control the dropdown's visibility
   */
  open?: boolean;

  /**
   * 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<never>;

  /**
   * 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[];

  size?: "medium" | "large" | "extra-large";
}

TagPickerButton API

TagPickerButton component - A TagPickerButton is an alternative to TagPickerInput that does not include an input field.

interface TagPickerButtonProps {
  /**
   * Controls the colors and borders of the combobox trigger.
   * @default 'outline'
   */
  appearance?: "outline" | "filled-darker" | "filled-lighter" | "underline";

  as?: "button";

  children?: any;

  className?: string;

  disabled?: boolean;

  ref?: Ref<HTMLButtonElement>;

  /**
   * Controls the size of the combobox faceplate
   * @default 'medium'
   */
  size?: "small" | "medium" | "large";

  style?: CSSProperties;
}

TagPickerControl API

TagPickerControl component - A TagPickerControl is a composite component that controls actions and state for a TagPicker.

interface TagPickerControlProps {
  as?: "div";

  children?: any;

  className?: string;

  /**
   * 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;

  ref?: Ref<HTMLDivElement>;

  /**
   * A secondary action should be a button-like element to be rendered right after
   * the trigger responsible for opening/closing the tag picker popover.
   */
  secondaryAction?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  style?: (CSSProperties & { '--fui-TagPickerControl-aside-width'?: string | number; });
}

TagPickerGroup API

TagPickerGroup component - A TagPickerGroup is a composite component that allows users to group tags together.

interface TagPickerGroupProps {
  as?: "div";

  children?: any;

  className?: string;

  ref?: Ref<HTMLDivElement>;

  style?: CSSProperties;
}

TagPickerInput API

TagPickerInput component - A TagPickerInput is a composite component that allows users to query tags.

interface TagPickerInputProps {
  /**
   * Controls the colors and borders of the combobox trigger.
   * @default 'outline'
   */
  appearance?: "outline" | "filled-darker" | "filled-lighter" | "underline";

  as?: "input";

  className?: string;

  /**
   * If set, the combobox will show an icon to clear the current value.
   */
  clearable?: boolean;

  disabled?: boolean;

  ref?: Ref<HTMLInputElement>;

  style?: CSSProperties;

  value?: string | number | readonly string[];
}

TagPickerList API

TagPickerList component - A TagPickerList is a composite component that allows users to display a list of tag options to be selected.

interface TagPickerListProps {
  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
   * @deprecated TagPickerList is always rendered within a TagPicker which manages active descendant focus.
   * This prop has no effect.
   */
  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;
}

TagPickerOption API

TagPickerOption component - A TagPickerOption is a composite component that allows users to select tags.

interface TagPickerOptionProps {
  as?: "div";

  children?: any;

  className?: string;

  media?: WithSlotShorthandValue<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  ref?: Ref<HTMLDivElement>;

  secondaryContent?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  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;

  value: string;
}

Implementation

CoreAI 0.12.0; Fluent 9.74.9.

Setup and dependencies.

Import components from @coreai-ds/react-components. CoreAI guidance above takes precedence over API demonstration examples.

Appearance

Loading interactive example. Full source is available below.

Download source
// 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 { tokens } from "@coreai-ds/react-components";
import type { TagPickerProps } from "@coreai-ds/react-components";
import * as React from 'react';
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    darkBG: {
        backgroundColor: tokens.colorNeutralBackgroundInverted,
        color: tokens.colorNeutralForegroundInverted2,
        padding: '20px',
        marginBlock: '10px',
        borderRadius: tokens.borderRadiusMedium,
    },
});

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

const Example = ({ appearance }: Pick<TagPickerProps, 'appearance'>) => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([options[0]]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    const labelColor = appearance === 'filled-lighter' || appearance === 'filled-darker'
        ? tokens.colorNeutralForegroundInverted2
        : tokens.colorNeutralForeground1;
    const label = <span style={{ color: labelColor }}>Select Employees</span>;
    return (<Field label={label} style={{ maxWidth: 400 }}>
      <TagPicker appearance={appearance} onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export const Appearance = (): JSXElement => {
    const styles = useStyles();
    return (<>
      <div>
        <h1>Outline</h1>
        <Example appearance="outline"/>
      </div>
      <div>
        <h1>Underline</h1>
        <Example appearance="underline"/>
      </div>
      <div className={styles.darkBG}>
        <h1>Filled Darker</h1>
        <Example appearance="filled-darker"/>
      </div>
      <div className={styles.darkBG}>
        <h1>Filled Lighter</h1>
        <Example appearance="filled-lighter"/>
      </div>
    </>);
};

export default Appearance;

Button

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerButton } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const Button = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerButton aria-label="Select Employees"/>
        </TagPickerControl>

        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default Button;

Disabled

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const Disabled = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>(options.slice(0, 4));
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker disabled onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default Disabled;

Expand Icon

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { ArrowDownFilled } from "@fluentui/react-icons";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const ExpandIcon = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([options[0]]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (event, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl expandIcon={<ArrowDownFilled />}>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default ExpandIcon;

Filtering

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { useTagPickerFilter } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const Filtering = (): JSXElement => {
    const [query, setQuery] = React.useState<string>('');
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    // disable auto focus when no query is present (e.g. opened by keyboard)
    // enable auto focus when query is present (e.g. opened by typing)
    const disableAutoFocus = query.length === 0;
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-matches') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
        setQuery('');
    };
    const children = useTagPickerFilter({
        query,
        options,
        noOptionsElement: <TagPickerOption value="no-matches">We couldn't find any matches</TagPickerOption>,
        renderOption: option => (<TagPickerOption secondaryContent="Microsoft FTE" key={option} media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option}>
        {option}
      </TagPickerOption>),
        filter: option => !selectedOptions.includes(option) && option.toLowerCase().includes(query.toLowerCase()),
    });
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions} disableAutoFocus={disableAutoFocus}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees" value={query} onChange={e => setQuery(e.target.value)}/>
        </TagPickerControl>

        <TagPickerList>{children}</TagPickerList>
      </TagPicker>
    </Field>);
};

export default Filtering;

Grouped

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";
import { TagPickerOptionGroup } from "@coreai-ds/react-components";

const managers = ['John Doe', 'Jane Doe', 'Max Mustermann', 'Erika Mustermann'];

const devs = ['Pierre Dupont', 'Amelie Dupont', 'Mario Rossi', 'Maria Rossi'];

export const Grouped = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const unSelectedManagers = managers.filter(option => !selectedOptions.includes(option));
    const unSelectedDevs = devs.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {unSelectedManagers.length === 0 && unSelectedDevs.length === 0 && (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
          {unSelectedManagers.length > 0 && (<TagPickerOptionGroup label="Managers">
              {unSelectedManagers.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                  {option}
                </TagPickerOption>))}
            </TagPickerOptionGroup>)}
          {unSelectedDevs.length > 0 && (<TagPickerOptionGroup label="Devs">
              {unSelectedDevs.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                  {option}
                </TagPickerOption>))}
            </TagPickerOptionGroup>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default Grouped;

No Popover

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";

export const NoPopover = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    const [inputValue, setInputValue] = React.useState('');
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (_, data) => {
        setSelectedOptions(data.selectedOptions);
    };
    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setInputValue(event.currentTarget.value);
    };
    const handleKeyDown = (event: React.KeyboardEvent) => {
        if (event.key === 'Enter' && inputValue) {
            setInputValue('');
            setSelectedOptions(curr => (curr.includes(inputValue) ? curr : [...curr, inputValue]));
        }
    };
    return (<Field label="Add Employees" style={{ maxWidth: 400 }}>
      <TagPicker noPopover onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map((option, index) => (<Tag key={index} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput value={inputValue} onChange={handleChange} onKeyDown={handleKeyDown} aria-label="Add Employees"/>
        </TagPickerControl>
      </TagPicker>
    </Field>);
};

export default NoPopover;

Secondary Action

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const SecondaryAction = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([options[0]]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (event, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const handleAllClear: React.MouseEventHandler = event => {
        setSelectedOptions([]);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl secondaryAction={<Button appearance="transparent" size="small" shape="rounded" onClick={handleAllClear}>
              All Clear
            </Button>}>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default SecondaryAction;

Single Line

Loading interactive example. Full source is available below.

Download source
// 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 { tagPickerGroupClassNames } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { Overflow } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { useOverflowCount } from "@coreai-ds/react-components";
import { ChevronUpRegular } from "@fluentui/react-icons";
import { ChevronDownRegular } from "@fluentui/react-icons";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { OverflowItem } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import type { TagProps } from "@coreai-ds/react-components";
import type { TagPickerInputProps } from "@coreai-ds/react-components";
import { useTagPickerContext_unstable } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    focusedExpandIcon: { alignSelf: 'flex-end' },
    countButton: { minWidth: 0 },
    control: {
        flexWrap: 'nowrap',
        display: 'flex',
        flexGrow: 1,
        minWidth: 0,
        overflow: 'hidden',
        [`& > .${tagPickerGroupClassNames.root}`]: {
            flexWrap: 'nowrap',
        },
        ':focus-within': {
            flexWrap: 'wrap',
            [`& > .${tagPickerGroupClassNames.root}`]: {
                flexWrap: 'wrap',
            },
        },
    },
});

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

type ExpandIconProps = {
    open: boolean;
    focus: boolean;
};

const ExpandIcon = (props: ExpandIconProps) => {
    const overflowCount = useOverflowCount();
    if (props.open) {
        return <ChevronUpRegular />;
    }
    if (overflowCount === 0 || props.focus) {
        return <ChevronDownRegular />;
    }
    return null;
};

const OverFlowCountTag = (props: TagProps) => {
    const overflowCount = useOverflowCount();
    const styles = useStyles();
    if (overflowCount === 0) {
        return null;
    }
    return (<Tag as="span" role={undefined} dismissible={false} aria-hidden tabIndex={-1} {...props} className={styles.countButton}>
      +{overflowCount}
    </Tag>);
};

type CustomTagPickerInputProps = TagPickerInputProps & {
    focus: boolean;
};

const CustomTagPickerInput = React.forwardRef<HTMLInputElement, CustomTagPickerInputProps>(({ focus, onMouseDown, placeholder, ...rest }, ref) => {
    const overflowCount = useOverflowCount();
    const selectedOptionsAmount = useTagPickerContext_unstable(ctx => ctx.selectedOptions.length);
    return (<TagPickerInput ref={ref} {...rest} placeholder={selectedOptionsAmount === 0 || (overflowCount > 0 && focus) ? placeholder : undefined}/>);
});

export const SingleLine = (): JSXElement => {
    const styles = useStyles();
    const [open, setOpen] = React.useState(false);
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
    const inputRef = React.useRef<HTMLInputElement>(null);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    const [hasFocus, setFocus] = React.useState(false);
    const handleOpenChange: TagPickerProps['onOpenChange'] = (_, data) => setOpen(data.open);
    const handleFocus = () => {
        setFocus(true);
    };
    const handleBlur = (event: React.FocusEvent) => {
        if (event.currentTarget.contains(event.relatedTarget as Node)) {
            return;
        }
        setFocus(false);
    };
    const handleOverflowCountTagMouseDown = (event: React.MouseEvent) => {
        event.preventDefault();
        inputRef.current?.focus();
    };
    return (<TagPicker open={open} onOpenChange={handleOpenChange} onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
      {/* 24 = min input size */}
      {/* 30 = padding right */}
      {/* 2 = gap between input and tags */}
      {/* 4 = gap between tags */}
      <Overflow minimumVisible={1} padding={24 + 30 + 2 + selectedOptions.length * 4}>
        <TagPickerControl style={{ maxWidth: 400 }} expandIcon={{
            className: hasFocus ? styles.focusedExpandIcon : undefined,
            children: <ExpandIcon focus={hasFocus} open={open}/>,
        }} onFocus={handleFocus} onBlur={handleBlur} className={styles.control}>
          <TagPickerGroup>
            {selectedOptions.map(option => (<OverflowItem id={option} key={option}>
                <Tag 
        // force style to display the tag even if it's overflowing when focused
        style={hasFocus ? { display: 'inline-grid' } : undefined} key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                  {option}
                </Tag>
              </OverflowItem>))}
            {!open && !hasFocus ? <OverFlowCountTag onMouseDown={handleOverflowCountTagMouseDown}/> : null}
          </TagPickerGroup>
          <CustomTagPickerInput ref={inputRef} focus={hasFocus} placeholder="Select Employees" aria-label="Select Employees"/>
        </TagPickerControl>
      </Overflow>
      <TagPickerList>
        {tagPickerOptions.length > 0
            ? tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))
            : 'No options available'}
      </TagPickerList>
    </TagPicker>);
};

export default SingleLine;

Single Select

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

export const SingleSelect = (): JSXElement => {
    const [selectedOption, setSelectedOption] = React.useState<string | undefined>();
    const selectedOptions = React.useMemo(() => (selectedOption ? [selectedOption] : []), [selectedOption]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        setSelectedOption(selectedOption === data.value ? undefined : data.value);
    };
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          {selectedOption && (<TagPickerGroup aria-label="Selected Employees">
              <Tag key={selectedOption} shape="rounded" media={<Avatar aria-hidden name={selectedOption} color="colorful"/>} value={selectedOption}>
                {selectedOption}
              </Tag>
            </TagPickerGroup>)}
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {options
            .filter(option => selectedOption !== option)
            .map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default SingleSelect;

Size

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import type { TagPickerProps } from "@coreai-ds/react-components";
import * as React from 'react';
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

const options = [
    'John Doe',
    'Jane Doe',
    'Max Mustermann',
    'Erika Mustermann',
    'Pierre Dupont',
    'Amelie Dupont',
    'Mario Rossi',
    'Maria Rossi',
];

const Example = ({ size }: Pick<TagPickerProps, 'size'>) => {
    const [selectedOptions, setSelectedOptions] = React.useState<string[]>([options[0]]);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions);
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker size={size} onOptionSelect={onOptionSelect} selectedOptions={selectedOptions}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option} shape="rounded" media={<Avatar aria-hidden name={option} color="colorful"/>} value={option}>
                {option}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent="Microsoft FTE" media={<Avatar shape="square" aria-hidden name={option} color="colorful"/>} value={option} key={option}>
                {option}
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export const Size = (): JSXElement => (<>
    <div>
      <h4>Extra Large</h4>
      <Example size="extra-large"/>
    </div>
    <div>
      <h4>Large</h4>
      <Example size="large"/>
    </div>
    <div>
      <h4>Medium</h4>
      <Example size="medium"/>
    </div>
  </>);

export default Size;

Truncated Text

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import type { TagPickerProps } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { TagPicker } from "@coreai-ds/react-components";
import { TagPickerControl } from "@coreai-ds/react-components";
import { TagPickerGroup } from "@coreai-ds/react-components";
import { Tag } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import { TagPickerInput } from "@coreai-ds/react-components";
import { TagPickerList } from "@coreai-ds/react-components";
import { TagPickerOption } from "@coreai-ds/react-components";

type Option = {
    value: string;
    fixedWidth?: boolean;
};

const options: Option[] = [
    { value: 'John Doe' },
    { value: 'Jane Doe' },
    { value: 'Max Mustermann' },
    { value: 'Erika Mustermann' },
    { value: 'Pierre Dupont' },
    { value: 'Amelie Dupont' },
    { value: 'Maria Rossi' },
    {
        value: 'This tag has text truncation based on a fixed width of 50px',
        fixedWidth: true,
    },
    {
        value: 'This tag has text truncation based on its container width. This is a long text that will be truncated when it reaches the end of the container.',
    },
];

const useStyles = makeStyles({
    tagTruncatedPrimaryText: {
        whiteSpace: 'nowrap',
        overflowX: 'hidden',
        textOverflow: 'ellipsis',
    },
    tagPrimaryTextFixedWidth: {
        width: '50px',
    },
    optionContent: {
        whiteSpace: 'nowrap',
        overflowX: 'hidden',
        textOverflow: 'ellipsis',
    },
    optionSecondaryContent: {
        whiteSpace: 'nowrap',
    },
});

export const TruncatedText = (): JSXElement => {
    const [selectedOptions, setSelectedOptions] = React.useState<Option[]>(options);
    const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => {
        if (data.value === 'no-options') {
            return;
        }
        setSelectedOptions(data.selectedOptions.map(option => options.find(o => o.value === option)!));
    };
    const tagPickerOptions = options.filter(option => !selectedOptions.includes(option));
    const styles = useStyles();
    return (<Field label="Select Employees" style={{ maxWidth: 400 }}>
      <TagPicker onOptionSelect={onOptionSelect} selectedOptions={selectedOptions.map(option => option.value)}>
        <TagPickerControl>
          <TagPickerGroup aria-label="Selected Employees">
            {selectedOptions.map(option => (<Tag key={option.value} shape="rounded" media={<Avatar aria-hidden name={option.value} color="colorful"/>} value={option.value} title={option.value} primaryText={{
                className: mergeClasses(styles.tagTruncatedPrimaryText, option.fixedWidth && styles.tagPrimaryTextFixedWidth),
            }}>
                {option.value}
              </Tag>))}
          </TagPickerGroup>
          <TagPickerInput aria-label="Select Employees"/>
        </TagPickerControl>
        <TagPickerList>
          {tagPickerOptions.length > 0 ? (tagPickerOptions.map(option => (<TagPickerOption secondaryContent={{ children: 'Microsoft FTE', className: styles.optionSecondaryContent }} media={<Avatar shape="square" aria-hidden name={option.value} color="colorful"/>} value={option.value} key={option.value} title={option.value} text={option.value}>
                <div className={styles.optionContent}>{option.value}</div>
              </TagPickerOption>))) : (<TagPickerOption value="no-options">No options available</TagPickerOption>)}
        </TagPickerList>
      </TagPicker>
    </Field>);
};

export default TruncatedText;