const CustomMessage = () => {
// consume `MessageContext`
const { message } = useMessageContext();
const finalAttachments = useMemo(
() =>
!message.shared_location && !message.attachments
? []
: !message.shared_location
? message.attachments
: [message.shared_location, ...(message.attachments ?? [])],
[message],
);
return (
<div>
{finalAttachments.length > 0 && (
<Attachment attachments={finalAttachments} />
)}
// render remaining custom Message UI component
</div>
);
};
<Chat client={client}>
<Channel channel={channel} Message={CustomMessage}>
<MessageList />
<MessageInput />
</Channel>
</Chat>;Attachments
Attachment renders a list of message attachments and chooses the UI based on attachment type. The table below shows which UI component is used:
| Attachment Type | UI Component | File type(s) (non-exhaustive) |
|---|---|---|
audio | Audio | MP3, WAV, M4A, FLAC, AAC |
file | File | DOC, DOCX, PDF, PPT, PPTX, TXT, XLS, XLSX |
gallery | Gallery | when a message has more than 1 'image' type attachment |
image | Image | HEIC, GIF, JPEG, JPG, PNG, TIFF, BMP |
video | ReactPlayer | MP4, OGG, WEBM, Quicktime(QTFF, QT, or MOV) |
voiceRecording | VoiceRecording | MP3, WAV |
geolocation | Geolocation | Not applicable |
Best Practices
- Combine
message.shared_locationwithmessage.attachmentswhen rendering. - Use component overrides only for the attachment types you customize.
- Keep image/video sizing within pixel-computable CSS values.
- Prefer CDN-based resizing for performance and bandwidth savings.
- Test attachments with mixed content to verify layout and cropping.
Attachments that don’t match the types above are treated as scraped content and should include og_scrape_url or title_link to render with Card. Otherwise they won’t render.
geolocation is a special case: the data comes from message.shared_location, not message.attachments.
Basic Usage
By default, Attachment is included in MessageSimple. For a custom Message UI component, import Attachment and render it conditionally when attachments exist.
UI Customization
Attachment accepts component overrides for each attachment type. Building on the example above, here’s how to customize a single attachment renderer:
const CustomImage = (props) => {
// render custom image component here
};
const CustomMessage = () => {
// consume `MessageContext`
const { message } = useMessageContext();
const finalAttachments = useMemo(
() =>
!message.shared_location && !message.attachments
? []
: !message.shared_location
? message.attachments
: [message.shared_location, ...(message.attachments ?? [])],
[message],
);
return (
<div>
{finalAttachments.length > 0 && (
<Attachment attachments={message.attachments} Image={CustomImage} />
)}
{/* render remaining custom Message UI component */}
</div>
);
};
<Chat client={client}>
<Channel channel={channel} Message={CustomMessage}>
<MessageList />
<MessageInput />
</Channel>
</Chat>;Or wrap Attachment directly:
const CustomAudioAttachment = (props) => {
// you can add any custom data (such as "customType" in this case)
if (props.og?.customType === "voice-memo")
return <div>my custom voice-memo component</div>;
return <Audio {...props} />;
};
const CustomAttachment = (props) => {
return <Attachment {...props} Audio={CustomAudioAttachment} />;
};
<Chat client={client}>
<Channel channel={channel} Attachment={CustomAttachment}>
<MessageList />
<MessageInput />
</Channel>
</Chat>;Image and video sizing
The following section describes how image and video sizes are computed.
Maximum size
You can control the maximum width and height of images and videos with the --str-chat__attachment-max-width CSS variable. The value of this variable must be a value that can be computed to a valid pixel value using the getComputedStyle method (for example: 300px, 10rem, calc(300px - var(--margin)), but not 100%). If you provide an invalid value, the image and video sizing can break, which can lead to scrolling issues inside the message list (for example the message list isn't scrolled to the bottom when you open a channel).
If you set an invalid value to the variable, you'll see a warning on the browser's console:

File size optimization
Based on the CSS settings provided for images and videos (see Maximum size section), the SDK will load an image (or thumbnail image in case of a video file) with a reduced file size while still providing sufficient image quality for the given dimensions. This will result in less network traffic and faster image load for users.
For example, if an image’s max size is 300px and the original is 965 × 1280, the SDK loads a smaller version via Stream’s CDN.
Aspect ratio
The SDK tries to preserve original aspect ratio, but there are exceptions where cropping occurs:
- If a message has multiple text lines and/or multiple attachments, the image/video can stretch to
max-width.
Example 1 - message with one line of text - image is displayed with original aspect ratio

Example 2 - message with multiple lines of text - image is cropped

- In Safari, portrait images/videos are stretched to
max-width.
Example 3 - portrait images in Safari - image is cropped

- If the image/video can’t fit the host element’s
max-width/max-heightconstraints without cropping.
File size optimization and maintaining aspect ratio uses features provided by Stream's CDN. If you're using your own CDN you'll likely have to provide your own implementation for this. You can do this using the imageAttachmentSizeHandler and videoAttachmentSizeHandler props.
If you plan to override attachment sizing with custom CSS, note:
The sizing logic for images/videos (via imageAttachmentSizeHandler and videoAttachmentSizeHandler) requires host elements (usually img and video) to have max-height/height and max-width values that resolve to pixels via getComputedStyle. See Maximum size.
Props
attachments
The message attachments to render, see Attachment Format in the general JavaScript docs.
| Type |
|---|
| array |
actionHandler
The handler function to call when an action is performed on an attachment, examples include canceling a /giphy command or shuffling the results.
| Type |
|---|
| (dataOrName?: string | FormData, value?: string, event?: React.BaseSyntheticEvent) => Promise<void> |
AttachmentActions
Custom UI component for displaying attachment actions.
| Type | Default |
|---|---|
| component | AttachmentActions |
Audio
Custom UI component for displaying an audio type attachment.
| Type | Default |
|---|---|
| component | Audio |
Card
Custom UI component for displaying a card type attachment.
| Type | Default |
|---|---|
| component | Card |
File
Custom UI component for displaying a file type attachment.
| Type | Default |
|---|---|
| component | File |
Gallery
Custom UI component for displaying a gallery of image type attachments.
| Type | Default |
|---|---|
| component | Gallery |
Geolocation
Custom UI component for displaying geolocation data.
| Type | Default |
|---|---|
| component | Geolocation |
Image
Custom UI component for displaying an image type attachment.
| Type | Default |
|---|---|
| component | Image |
Media
Custom UI component for displaying a video type attachment, defaults to use the react-player
dependency.
| Type | Default |
|---|---|
| component | ReactPlayer |