Message Input UI
How-to Guide for Building a Custom Message Input
The React Chat component library provides a highly customizable MessageInput
component. We'll outline the various ways in which you
can customize the look and behavior of this component, ranging from simple style changes, all the way to creating a completely
new input with custom logic.
Styling
The default Input UI component comes with predefined styles and CSS classes. The easiest way
to customize styling is to write your own CSS and override the default values. To get a sense of all the classes and styles
applied to the MessageInput
and its child components, you can either inspect the DOM inside your browser.
For example, here's how you can override the styles for the underlying textarea
and make its background light blue:
.str-chat .str-chat__textarea > textarea {
background-color: #99ccff;
}
Customize Functionality
The MessageInput
component supports a variety of props that let you customize its behavior in various ways.
You can utilize props to change basic behaviors of the underlying textarea
element (ex: the grow
prop lets you specify
whether the input field should automatically increase in height when the message wraps the input), as well as override
functions and more complex and logic-based parts of the component (ex: the doImageUploadRequest
prop lets you supply a custom
function that handles the uploading of image attachments).
For a complete overview of props, take a look at the MessageInput props section.
Override UI Components
You can override the UI components rendered inside the MessageInput
via Channel
props. These are then injected into the ComponentContext
, and in turn are consumed by the
MessageInput
.
Here's an example of overriding the default EmojiIcon
component:
const CustomEmojiIcon = () => {
const { t } = useTranslationContext();
return (
<div>
<img src='icon.svg' alt={t('Open emoji picker')} />
</div>
);
};
<Chat client={client}>
<Channel channel={channel} EmojiIcon={CustomEmojiIcon}>
<MessageList />
<MessageInput />
</Channel>
</Chat>;
Custom Triggers
The MessageInput
component supports autocomplete triggers. When you type a special character (by default: @
for mentions,
:
for emoji, /
for commands), a suggestion list pops up and auto-completes results based on the input text. The default
behavior of these triggers can be extended and/or overridden. Meaning, you can add your own custom trigger, or modify the
behavior of the default triggers.
The Channel
component exposes a TriggerProvider
prop, which defaults to the DefaultTriggerProvider
component. This component injects the approved set of triggers into the autocompleteTriggers
value of the MessageInputContext
. This value is consumed by the ChatAutoComplete
component and its children to ensure
proper trigger functionality. By injecting a custom TriggerProvider
component, you can adjust the behavior of any of these
triggers.
Here's an example of a custom TriggerProvider
that overrides the default values with the #
character:
import React from 'react';
import { MessageInputContextProvider, useMessageInputContext } from 'stream-chat-react';
const options = ['some', 'thing', 'that', 'totally', 'works'];
const CustomSuggestionItem = (props) => <div>{props.entity.name}</div>;
const customTrigger = {
component: CustomSuggestionItem,
dataProvider: (query, _, onReady) => {
const filteredOptions = options
.filter((option) => option.includes(query))
.map((option) => ({ name: option }));
onReady(filteredOptions, query);
},
output: (entity) => ({
caretPosition: 'next',
key: entity.name,
text: entity.name,
}),
};
const customTriggers = {
'#': customTrigger,
};
export const CustomTriggerProvider = ({ children }) => {
const currentContextValue = useMessageInputContext();
const updatedContextValue = {
...currentContextValue,
autocompleteTriggers: customTriggers,
};
return (
<MessageInputContextProvider value={updatedContextValue}>
{children}
</MessageInputContextProvider>
);
};
The CustomTriggerProvider
component is then added as a prop onto Channel
to override the default trigger behavior.
<Chat client={client}>
<Channel channel={channel} TriggerProvider={CustomTriggerProvider}>
<MessageList />
<MessageInput />
</Channel>
</Chat>
Building the Input UI
The MessageInput
component wraps and provides all the stateful logic needed to build your own Input UI component.
Both Channel
and MessageInput
accept an Input
prop, which lets you pass in your own UI component that consumes the
MessageInputContext
and handles the input's display.
If an Input
prop is not provided, MessageInput
renders MessageInputFlat
by default.
We provide a useMessageInputContext
custom hook, which lets you access all the stateful data and functionality needed
to create your own custom Input UI component.
Use MessageInputFlat
as a guide to help you build your own custom Input UI component.
The below example shows how to build a simple Input UI component, which calls the useMessageInputContext
hook
and use its return values to build functionality:
import {
ChatAutoComplete,
EmojiIconLarge,
EmojiPicker,
SendButton,
Tooltip,
useMessageInputContext,
useTranslationContext,
} from 'stream-chat-react';
export const CustomMessageInput = () => {
const { t } = useTranslationContext();
const {
closeEmojiPicker,
emojiPickerIsOpen,
handleEmojiKeyDown,
handleSubmit,
openEmojiPicker,
} = useMessageInputContext();
return (
<div className='str-chat__input-flat str-chat__input-flat--send-button-active'>
<div className='str-chat__input-flat-wrapper'>
<div className='str-chat__input-flat--textarea-wrapper'>
<ChatAutoComplete />
<div className='str-chat__emojiselect-wrapper'>
<Tooltip>
{emojiPickerIsOpen ? t('Close emoji picker') : t('Open emoji picker')}
</Tooltip>
<span
className='str-chat__input-flat-emojiselect'
onClick={emojiPickerIsOpen ? closeEmojiPicker : openEmojiPicker}
onKeyDown={handleEmojiKeyDown}
role='button'
tabIndex={0}
>
<EmojiIconLarge />
</span>
</div>
<EmojiPicker />
</div>
<SendButton sendMessage={handleSubmit} />
</div>
</div>
);
};
Once you've created your custom input component, you render it by adding the Input
prop to either the Channel
or MessageInput
component. Adding onto Channel
will store the component in the ComponentContext
, whereas adding onto MessageInput
will
override any context value.
<Chat client={client}>
<Channel channel={channel} Input={CustomMessageInput}>
<MessageList />
<MessageInput />
</Channel>
</Chat>