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 { InteractionTagProps } from "@coreai-ds/react-components";
import type { JSXElement } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import * as React from 'react';

export const Default = (props: Partial<InteractionTagProps>): JSXElement => (<InteractionTag {...props}>
    <InteractionTagPrimary>Primary text</InteractionTagPrimary>
  </InteractionTag>);

export default Default;

Resources

Behavior and states

  • Interaction tags can include one primary action in addition to dismissal.
  • The primary action should directly relate to the represented object.
  • The secondary action is always dismiss.
  • Interaction states are always enabled because interaction tags always have actions.
  • Tags create a visual pattern that lets people know they can change or remove the selection.
  • To show system-generated data that people can’t change, use a badge instead.
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.

InteractionTag API

InteractionTag component - a visual representation of an attribute with primary and secondary actions. Can have InteractionTagPrimary and InteractionTagSecondary components as its children.

interface InteractionTagProps {
  /**
   * An InteractionTag can have filled, outlined or brand experience.
   * @default 'filled'
   */
  appearance?: "brand" | "filled" | "outline";

  as?: "div";

  children?: any;

  className?: string;

  /**
   * An InteractionTag can show that it cannot be interacted with.
   * @default false
   */
  disabled?: boolean;

  ref?: Ref<HTMLDivElement>;

  /**
   * An InteractionTag can be selected.
   * Note: This prop only changes the appearance of the tag at the moment. A future PR will add the integration with TagGroup.
   * @default false
   */
  selected?: boolean;

  /**
   * An InteractionTag can have rounded or circular shape.
   * @default 'round'
   */
  shape?: "circular" | "rounded";

  /**
   * An InteractionTag has three sizes.
   * @default 'medium'
   */
  size?: "small" | "medium" | "extra-small";

  style?: CSSProperties;

  /**
   * Unique value identifying the tag within a TagGroup
   */
  value?: string;
}

InteractionTagPrimary API

InteractionTagPrimary component - used as the first child of the InteractionTag component. Provides visual attributes such as media, icon, primary and secondary text, as well as the ability to attach a primary action.

interface InteractionTagPrimaryProps {
  as?: "button";

  children?: any;

  className?: string;

  /**
   * Whether the `InteractionTag` component has a `Secondary` component that provides an secondary action.
   * If `true`, the `InteractionTagPrimary` component will adjust its styles to accommodate the `Secondary` component.
   * @default false
   */
  hasSecondaryAction?: boolean;

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

  /**
   * Slot for a visual element, usually an avatar
   */
  media?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  /**
   * Main text for the InteractionTagPrimary button. Children of the root slot are automatically rendered here
   */
  primaryText?: 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<HTMLButtonElement>;

  /**
   * Secondary text that describes or complements the main text
   */
  secondaryText?: 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;
}

InteractionTagSecondary API

InteractionTagSecondary component - used as the second child of the InteractionTag component and provides the secondary action, which is dismiss by default.

interface InteractionTagSecondaryProps {
  as?: "button";

  children?: any;

  className?: string;

  ref?: Ref<HTMLButtonElement>;

  style?: CSSProperties;
}

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 { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { bundleIcon } from "@fluentui/react-icons";
import { CalendarMonthFilled } from "@fluentui/react-icons";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import * as React from 'react';

const useContainerStyles = makeStyles({
    container: {
        columnGap: '10px',
        display: 'flex',
    },
});

const CalendarMonth = bundleIcon(CalendarMonthFilled, CalendarMonthRegular);

export const Appearance = (): JSXElement => {
    const styles = useContainerStyles();
    return (<div className={styles.container}>
      <InteractionTag>
        <InteractionTagPrimary icon={<CalendarMonth />} hasSecondaryAction>
          filled
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag appearance="outline">
        <InteractionTagPrimary icon={<CalendarMonth />} hasSecondaryAction>
          outline
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag appearance="brand">
        <InteractionTagPrimary icon={<CalendarMonth />} hasSecondaryAction>
          brand
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
    </div>);
};

export default Appearance;

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 { makeStyles } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import * as React from 'react';

const useContainerStyles = makeStyles({
    container: {
        columnGap: '10px',
        display: 'flex',
    },
});

export const Disabled = (): JSXElement => {
    const styles = useContainerStyles();
    return (<div className={styles.container}>
      <InteractionTag disabled>
        <InteractionTagPrimary secondaryText="appearance=filled" icon={<CalendarMonthRegular />} hasSecondaryAction>
          Disabled
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag disabled appearance="outline">
        <InteractionTagPrimary secondaryText="appearance=outline" icon={<CalendarMonthRegular />} hasSecondaryAction>
          Disabled
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag disabled appearance="brand">
        <InteractionTagPrimary secondaryText="appearance=brand" icon={<CalendarMonthRegular />} hasSecondaryAction>
          Disabled
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
    </div>);
};

export default Disabled;

Dismiss

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 { TagGroupProps } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { TagGroup } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";

const initialTags = [
    { value: '1', children: 'Tag 1' },
    { value: '2', children: 'Tag 2' },
    { value: '3', children: 'Tag 3' },
];

/**
 * focus management for the reset button
 */
const useResetExample = (visibleTagsLength: number) => {
    const resetButtonRef = React.useRef<HTMLButtonElement>(null);
    const firstTagRef = React.useRef<HTMLButtonElement>(null);
    const prevVisibleTagsLengthRef = React.useRef<number>(visibleTagsLength);
    React.useEffect(() => {
        if (visibleTagsLength === 0) {
            resetButtonRef.current?.focus();
        }
        else if (prevVisibleTagsLengthRef.current === 0) {
            firstTagRef.current?.focus();
        }
        prevVisibleTagsLengthRef.current = visibleTagsLength;
    }, [visibleTagsLength]);
    return { firstTagRef, resetButtonRef };
};

const useStyles = makeStyles({
    container: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: '10px',
    },
    tagGroup: {
        flexWrap: 'wrap',
    },
    resetButton: {
        width: 'fit-content',
    },
});

export const Dismiss = (): JSXElement => {
    const [visibleTags, setVisibleTags] = React.useState(initialTags);
    const removeItem: TagGroupProps['onDismiss'] = (_e, { value }) => {
        setVisibleTags([...visibleTags].filter(tag => tag.value !== value));
    };
    const resetItems = () => setVisibleTags(initialTags);
    const { firstTagRef, resetButtonRef } = useResetExample(visibleTags.length);
    const styles = useStyles();
    return (<div className={styles.container}>
      {visibleTags.length !== 0 && (<TagGroup className={styles.tagGroup} onDismiss={removeItem} aria-label="Dismiss example">
          {visibleTags.map((tag, index) => (<InteractionTag value={tag.value} key={tag.value}>
              <InteractionTagPrimary hasSecondaryAction ref={index === 0 ? firstTagRef : null}>
                {tag.children}
              </InteractionTagPrimary>
              <InteractionTagSecondary aria-label="remove"/>
            </InteractionTag>))}
        </TagGroup>)}

      <Button onClick={resetItems} ref={resetButtonRef} disabled={visibleTags.length !== 0} className={styles.resetButton} size="small">
        Reset the example
      </Button>
    </div>);
};

export default Dismiss;

Has Primary 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 { makeStyles } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { Popover } from "@coreai-ds/react-components";
import { PopoverTrigger } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { PopoverSurface } from "@coreai-ds/react-components";
import { Link } from "@coreai-ds/react-components";
import { Tooltip } from "@coreai-ds/react-components";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import * as React from 'react';

const useStyles = makeStyles({
    popover: {
        width: '360px',
        maxWidth: '100%',
        height: 'fit-content',
    },
});

export const HasPrimaryAction = (): JSXElement => {
    const styles = useStyles();
    return (<InteractionTag>
      <Popover trapFocus>
        <PopoverTrigger>
          <InteractionTagPrimary hasSecondaryAction>Golden retriever</InteractionTagPrimary>
        </PopoverTrigger>
        <PopoverSurface className={styles.popover}>
          <Link href="https://en.wikipedia.org/wiki/Golden_Retriever">Find out more on wiki</Link>
          <ul>
            <li>Size: Medium to large-sized dog breed. </li>
            <li>Coat: Luxurious double coat with a dense, water-repellent outer layer and a soft, dense undercoat.</li>
            <li>Color: Typically a luscious golden or cream color, with variations in shade.</li>
            <li>Build: Sturdy and well-proportioned body with a friendly and intelligent expression.</li>
          </ul>
        </PopoverSurface>
      </Popover>
      <Tooltip content="dismiss" relationship="label">
        <InteractionTagSecondary />
      </Tooltip>
    </InteractionTag>);
};

export default HasPrimaryAction;

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 { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import * as React from 'react';

export const Icon = (): JSXElement => (<InteractionTag>
    <InteractionTagPrimary icon={<CalendarMonthRegular />}>Primary text</InteractionTagPrimary>
  </InteractionTag>);

export default Icon;

Media

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 { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import * as React from 'react';

export const Media = (): JSXElement => (<InteractionTag>
    <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>}>
      Primary text
    </InteractionTagPrimary>
  </InteractionTag>);

export default Media;

SecondaryText

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 { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import * as React from 'react';

export const SecondaryText = (): JSXElement => (<InteractionTag>
    <InteractionTagPrimary secondaryText="Secondary text">Primary text</InteractionTagPrimary>
  </InteractionTag>);

export default SecondaryText;

Selected

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 { makeResetStyles } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { bundleIcon } from "@fluentui/react-icons";
import { CalendarMonthFilled } from "@fluentui/react-icons";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import * as React from 'react';

const useContainerStyles = makeResetStyles({
    columnGap: '10px',
    display: 'flex',
});

const CalendarMonth = bundleIcon(CalendarMonthFilled, CalendarMonthRegular);

export const Selected = (): JSXElement => {
    const containerStyles = useContainerStyles();
    return (<div className={containerStyles}>
      <InteractionTag selected>
        <InteractionTagPrimary secondaryText="appearance=filled" icon={<CalendarMonth />} hasSecondaryAction>
          Selected
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag selected appearance="outline">
        <InteractionTagPrimary secondaryText="appearance=outline" icon={<CalendarMonth />} hasSecondaryAction>
          Selected
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
      <InteractionTag selected appearance="brand">
        <InteractionTagPrimary secondaryText="appearance=brand" icon={<CalendarMonth />} hasSecondaryAction>
          Selected
        </InteractionTagPrimary>
        <InteractionTagSecondary aria-label="remove"/>
      </InteractionTag>
    </div>);
};

export default Selected;

Shape

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 { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import * as React from 'react';

const useContainerStyles = makeStyles({
    innerWrapper: {
        alignItems: 'start',
        columnGap: '10px',
        display: 'flex',
        flexWrap: 'wrap',
    },
    outerWrapper: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: '10px',
    },
});

export const Shape = (): JSXElement => {
    const containerStyles = useContainerStyles();
    return (<div className={containerStyles.outerWrapper}>
      <div className={containerStyles.innerWrapper}>
        <InteractionTag>
          <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>}>
            Rounded
          </InteractionTagPrimary>
        </InteractionTag>
        <InteractionTag shape="circular">
          <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>}>
            Circular
          </InteractionTagPrimary>
        </InteractionTag>
      </div>

      <div className={containerStyles.innerWrapper}>
        <InteractionTag>
          <InteractionTagPrimary icon={<CalendarMonthRegular />} secondaryText="Secondary text" hasSecondaryAction>
            Rounded
          </InteractionTagPrimary>
          <InteractionTagSecondary aria-label="remove"/>
        </InteractionTag>
        <InteractionTag shape="circular">
          <InteractionTagPrimary icon={<CalendarMonthRegular />} secondaryText="Secondary text" hasSecondaryAction>
            Circular
          </InteractionTagPrimary>
          <InteractionTagSecondary aria-label="remove"/>
        </InteractionTag>
      </div>
    </div>);
};

export default Shape;

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 { makeStyles } from "@coreai-ds/react-components";
import { InteractionTag } from "@coreai-ds/react-components";
import { InteractionTagPrimary } from "@coreai-ds/react-components";
import { Avatar } from "@coreai-ds/react-components";
import { InteractionTagSecondary } from "@coreai-ds/react-components";
import { CalendarMonthRegular } from "@fluentui/react-icons";
import * as React from 'react';

const useContainerStyles = makeStyles({
    innerWrapper: {
        alignItems: 'start',
        columnGap: '10px',
        display: 'flex',
        flexWrap: 'wrap',
    },
    outerWrapper: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: '10px',
    },
});

export const Size = (): JSXElement => {
    const styles = useContainerStyles();
    return (<div className={styles.outerWrapper}>
      {/* medium */}
      <div className={styles.innerWrapper}>
        <InteractionTag>
          <InteractionTagPrimary>Medium</InteractionTagPrimary>
        </InteractionTag>

        <InteractionTag>
          <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>} hasSecondaryAction>
            Medium dismissible
          </InteractionTagPrimary>
          <InteractionTagSecondary aria-label="dismiss"/>
        </InteractionTag>

        <InteractionTag shape="circular">
          <InteractionTagPrimary icon={<CalendarMonthRegular />}>Medium circular</InteractionTagPrimary>
        </InteractionTag>
      </div>

      {/* small */}
      <div className={styles.innerWrapper}>
        <InteractionTag size="small">
          <InteractionTagPrimary>Small</InteractionTagPrimary>
        </InteractionTag>

        <InteractionTag size="small">
          <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>} hasSecondaryAction>
            Small dismissible
          </InteractionTagPrimary>
          <InteractionTagSecondary aria-label="dismiss"/>
        </InteractionTag>

        <InteractionTag size="small" shape="circular">
          <InteractionTagPrimary icon={<CalendarMonthRegular />}>Small circular</InteractionTagPrimary>
        </InteractionTag>
      </div>

      {/* extra-small */}
      <div className={styles.innerWrapper}>
        <InteractionTag size="extra-small">
          <InteractionTagPrimary>Extra small</InteractionTagPrimary>
        </InteractionTag>

        <InteractionTag size="extra-small">
          <InteractionTagPrimary media={<Avatar name="Katri Athokas" badge={{ status: 'busy' }}/>} hasSecondaryAction>
            Extra small dismissible
          </InteractionTagPrimary>
          <InteractionTagSecondary aria-label="dismiss"/>
        </InteractionTag>

        <InteractionTag size="extra-small" shape="circular">
          <InteractionTagPrimary icon={<CalendarMonthRegular />}>Extra small circular</InteractionTagPrimary>
        </InteractionTag>
      </div>
    </div>);
};

export default Size;