# Attachment Selector

`<AttachmentSelector/>` is the button before the composer's input. It opens a menu to upload a file, create a poll, share a location or pick a command. The menu is an action set: a list of actions, each with a type and a component. [Attachments and uploads](https://getstream.io/chat/docs/sdk/react/features/rich-content/attachments-and-uploads/) explains uploads, and [Actions](https://getstream.io/chat/docs/sdk/react/ui/components/actions/#how-an-action-set-works) explains how action sets work.

## Best practices

- Change the menu by passing your own `attachmentSelectorActionSet`, built from `defaultAttachmentSelectorActionSet`. Register the selector that renders it under the `AttachmentSelector` key.
- Decide who can upload, create polls and share locations on the server and in `client.config`. The selector offers a built-in action only when the user can use it, and that filter can't be turned off.
- Give a custom action a `type` of its own. The four built-in types are filtered, whatever component you give them.
- Set `getModalPortalDestination` when the selector renders outside a `<Channel/>`, or when the channel's container clips its dialogs.

## Default behavior

The default menu has four actions, in this order:

| Type            | Label    | What it does                              | Offered when                                                                        |
| --------------- | -------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| `uploadFile`    | File     | Opens the file picker                     | The user can upload files, as [Enabling features](#enabling-features) explains      |
| `createPoll`    | Poll     | Opens the poll creation dialog            | Polls are turned on, the user has the `send-poll` capability, and it's not a thread |
| `addLocation`   | Location | Opens the location sharing dialog         | Location sharing is turned on, and it's not a thread                                |
| `selectCommand` | Commands | Opens a submenu of the channel's commands | The channel type has commands                                                       |

`<AttachmentSelector/>` applies this filter itself, every time it renders, to the default set and to any set you pass. An action of any other type is always offered.

The File item stays in the menu, disabled, once the message reaches the file limit (`messageComposer.attachments.maxNumberOfFilesPerMessage` in `client.config`). [Uploads](https://getstream.io/chat/docs/sdk/react/state/writing-messages/attachments/#uploads) says which files count.

What the selector renders depends on the actions it offers:

- **No actions:** nothing.
- **One action that has a `StandaloneButton`:** that button alone, with no menu. The `uploadFile` action has `FileUploadButton` by default, so File alone renders a plain upload button. [Standalone buttons](#standalone-buttons) explains them.
- **Otherwise:** the button and its menu. A lone action without a `StandaloneButton` is the only item of the menu.

The button that opens the menu is disabled during a slow-mode cooldown. While a command is selected, the selector is hidden with a CSS transition and taken out of the tab order. A dialog opened from the menu closes once its action is no longer offered, or once a standalone button replaces the menu.

While the composer edits a message (it renders inside that message's `MessageContext`), the default layout renders `<SimpleAttachmentSelector/>` in place of `<AttachmentSelector/>`, which shows `FileUploadButton` alone, under the File action's rule and file limit.

## Enabling features

Each action follows the settings below. [Configuration](https://getstream.io/chat/docs/sdk/react/state/state-and-entities/configuration/#who-decides-what) explains how the server and `client.config` combine.

- **File:** offered while both of these hold. See [Attachments and uploads](https://getstream.io/chat/docs/sdk/react/features/rich-content/attachments-and-uploads/#turning-it-on-or-off).
  - Attachments are enabled. That needs `messageComposer.attachments.enabled` in `client.config`, and the channel type's `uploads` setting, set in the Stream Dashboard or through the server API. The `uploads` setting doesn't count when `messageComposer.attachments.customCdn` in `client.config` is `true`.
  - `messageComposer.attachments.customCdn` in `client.config` is `true`, or the user has the `upload-file` capability.
- **Poll:** `messageComposer.polls.enabled` in `client.config`, the channel type's `polls` setting (set in the Stream Dashboard or through the server API), and the `send-poll` capability. See [Polls](https://getstream.io/chat/docs/sdk/react/features/rich-content/polls/).
- **Location:** `messageComposer.location.enabled` in `client.config`, and the channel type's `shared_locations` setting, set in the Stream Dashboard or through the server API. See [Location sharing](https://getstream.io/chat/docs/sdk/react/features/rich-content/location-sharing/).
- **Commands:** the channel type's `commands`, set in the Stream Dashboard or through the server API. See [Commands and Giphy](https://getstream.io/chat/docs/sdk/react/features/rich-content/commands-and-giphy/).

The filter decides only what the selector offers. It doesn't grant what an action needs:

- An action with a `type` of its own is offered to every user, such as an upload action for a user without the `upload-file` capability.
- `AttachmentManager.uploadFiles()` still drops files while `attachmentManager.isUploadEnabled` is `false`, so an upload action that goes through the file input or `AttachmentManager.uploadFiles()` uploads nothing.
- Stream's server still refuses a poll from a user without the `send-poll` capability.

## Custom action sets

`<AttachmentSelector/>` props:

| Prop                          | Type                           | Default                              | Description                                                                                                                                                                                 |
| ----------------------------- | ------------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attachmentSelectorActionSet` | `AttachmentSelectorAction[]`   | `defaultAttachmentSelectorActionSet` | The actions of the menu, top to bottom. The filter in [Default behavior](#default-behavior) still applies.                                                                                  |
| `buttonProps`                 | `Omit<ButtonProps, "onClick">` | none                                 | Props for the button that opens the menu, or for a standalone button. The `disabled` you pass is ignored: the menu button and a standalone button are disabled during a slow-mode cooldown. |
| `getModalPortalDestination`   | `() => HTMLElement \| null`    | the `<Channel/>` container           | The element the action dialogs render into. See [Custom modal portal destination](#custom-modal-portal-destination).                                                                        |

An `AttachmentSelectorAction` has these fields:

| Field              | Type                                                                         | Description                                                                                                                                                         |
| ------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`             | `"uploadFile" \| "createPoll" \| "addLocation" \| "selectCommand" \| string` | What the action is. Also the key passed to `openModalForAction`.                                                                                                    |
| `ActionButton`     | `ComponentType<AttachmentSelectorActionProps>`                               | The menu item. It gets `openModalForAction(type)`, and `submenuItems` and `submenuHeader` when the action has a submenu.                                            |
| `ModalContent`     | `ComponentType<AttachmentSelectorModalContentProps>`                         | What `openModalForAction(type)` opens in a modal. It gets `close`.                                                                                                  |
| `Submenu`          | `ContextMenuSubmenu`                                                         | A submenu, as the commands action has.                                                                                                                              |
| `Header`           | `ContextMenuHeaderComponent`                                                 | The header of the submenu.                                                                                                                                          |
| `id`               | `string`                                                                     | An identifier of your own. The selector doesn't read it.                                                                                                            |
| `StandaloneButton` | `ComponentType<AttachmentSelectorStandaloneButtonProps>`                     | What renders in place of the menu when this is the only action offered. `uploadFile` defaults to `FileUploadButton`. See [Standalone buttons](#standalone-buttons). |

A menu item closes the menu with `closeMenu()` from `useContextMenuContext()`. The default items are exported as `DefaultAttachmentSelectorComponents`: `File`, `Poll`, `Location` and `Command`.

This selector leaves out Location:

```tsx
import type { PropsWithChildren } from "react";
import {
  AttachmentSelector,
  defaultAttachmentSelectorActionSet,
  WithComponents,
} from "stream-chat-react";

const actionSet = defaultAttachmentSelectorActionSet.filter(
  ({ type }) => type !== "addLocation",
);

const AttachmentSelectorWithoutLocation = () => (
  <AttachmentSelector attachmentSelectorActionSet={actionSet} />
);

const overrides = { AttachmentSelector: AttachmentSelectorWithoutLocation };

export const WithoutLocationAction = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

[Standalone buttons](#standalone-buttons) adds an action of its own, and the [Product card](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/custom-attachments/product-card/#adding-a-product-from-the-composer) recipe adds one that opens a picker in a dialog with `openModalForAction()`.

## Standalone buttons

When the selector offers only one action and that action has a `StandaloneButton`, the selector renders that button instead of a menu with one item.

- **Where it renders:** inside the selector's root element, `.str-chat__attachment-selector`, in place of the menu button. It collapses while a command is selected.
- **Props:** `buttonProps`, the selector's `buttonProps` with `disabled` set while a slow-mode cooldown runs. The type is `AttachmentSelectorStandaloneButtonProps`.
- **No dialog:** there's no menu, so it can't call `openModalForAction`.
- **Width:** the selector's space beside the input fits a round icon button. A wider button needs the `--str-chat__message-composer-attachment-slot-width` CSS variable.

The `uploadFile` action's standalone button is `FileUploadButton`. It opens the file picker, and it's disabled at the file limit. An action with a `type` of your own has no standalone button by default; an upload action of your own sets `StandaloneButton: FileUploadButton` to get the same plain button. [Enabling features](#enabling-features) says what such an action doesn't grant.

In this example, the set's only action is one of your own, which inserts a bug report template, so the selector always renders its labeled standalone button. The action still needs an `ActionButton` for the menu, which shows once the set has more actions:

```tsx
import type { PropsWithChildren } from "react";
import {
  AttachmentSelector,
  Button,
  ContextMenuButton,
  useContextMenuContext,
  useMessageComposerController,
  WithComponents,
} from "stream-chat-react";
import type {
  AttachmentSelectorAction,
  AttachmentSelectorStandaloneButtonProps,
} from "stream-chat-react";

const template = "Steps to reproduce:\nExpected:\nActual:";

const BugReportMenuItem = () => {
  const { closeMenu } = useContextMenuContext();
  const messageComposer = useMessageComposerController();

  return (
    <ContextMenuButton
      onClick={() => {
        void messageComposer.textComposer.insertText({ text: template });
        closeMenu();
      }}
    >
      Bug report
    </ContextMenuButton>
  );
};

const BugReportButton = ({
  buttonProps,
}: AttachmentSelectorStandaloneButtonProps) => {
  const messageComposer = useMessageComposerController();

  return (
    <Button
      {...buttonProps}
      appearance="outline"
      onClick={() =>
        void messageComposer.textComposer.insertText({ text: template })
      }
      size="lg"
      variant="secondary"
    >
      Bug report
    </Button>
  );
};

const actionSet: AttachmentSelectorAction[] = [
  {
    ActionButton: BugReportMenuItem,
    StandaloneButton: BugReportButton,
    type: "bugReportTemplate",
  },
];

const BugReportSelector = () => (
  <AttachmentSelector attachmentSelectorActionSet={actionSet} />
);

const overrides = { AttachmentSelector: BugReportSelector };

export const WithBugReportButton = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

```css
.str-chat__message-composer {
  --str-chat__message-composer-attachment-slot-width: 8rem;
}
```

## Poll and location dialogs

The `createPoll` and `addLocation` actions open the `PollCreationDialog` and `ShareLocationDialog` overrides, unless the action has a `ModalContent` of its own. Both dialogs get `close`. Opening the poll dialog starts a new poll in the composer's `pollComposer`.

```tsx
import type { PropsWithChildren } from "react";
import type { PollCreationDialogProps } from "stream-chat-react";
import { PollCreationDialog, WithComponents } from "stream-chat-react";

const PollDialogWithIntro = (props: PollCreationDialogProps) => (
  <div className="poll-dialog">
    <p>Polls close after a week.</p>
    <PollCreationDialog {...props} />
  </div>
);

const overrides = { PollCreationDialog: PollDialogWithIntro };

export const WithPollDialogIntro = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

The dialogs' own props are on [Poll components](https://getstream.io/chat/docs/sdk/react/component-reference/poll-components/) and [Location sharing](https://getstream.io/chat/docs/sdk/react/features/rich-content/location-sharing/).

## Custom modal portal destination

The action dialogs render into the `<Channel/>` container, the element with the `str-chat__channel` id. Outside a `<Channel/>`, or when that container clips them, pass `getModalPortalDestination`:

```tsx
import { AttachmentSelector } from "stream-chat-react";

const getModalPortalDestination = () =>
  document.getElementById("chat-modal-root");

export const AttachmentSelectorWithModalRoot = () => (
  <AttachmentSelector getModalPortalDestination={getModalPortalDestination} />
);
```

Register it under the `AttachmentSelector` key. [Dialogs](https://getstream.io/chat/docs/sdk/react/ui/components/dialogs/#modals) explains modals.

## AttachmentSelectorContext

The menu items of `<AttachmentSelector/>` can read `AttachmentSelectorContext` with `useAttachmentSelectorContext()`:

| Value       | Type                       | Description                                                                                                                                        |
| ----------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fileInput` | `HTMLInputElement \| null` | The hidden file input of the selector. Call `fileInput.click()` to open the file picker. `null` until the input renders, and outside the selector. |

`<SimpleAttachmentSelector/>` and standalone buttons don't provide this context.

## Hooks

- [`useAttachmentManagerState`](https://getstream.io/chat/docs/sdk/react/reference/hooks/composer-hooks/#useattachmentmanagerstate): whether uploads, polls and locations are turned on, and how many files still fit.
- [`useCanCreatePoll`](https://getstream.io/chat/docs/sdk/react/reference/hooks/composer-hooks/#usecancreatepoll): whether the poll being composed can be sent.
- [`useMessageComposerCommands`](https://getstream.io/chat/docs/sdk/react/reference/hooks/composer-hooks/#usemessagecomposercommands): the channel's commands, as the commands submenu shows them.
- [`useChannelCapabilities`](https://getstream.io/chat/docs/sdk/react/reference/hooks/channel-and-thread-hooks/#usechannelcapabilities): the user's capabilities, such as `upload-file` and `send-poll`.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/attachment-selector/](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/attachment-selector/).