React Native Gifted Chat
The most complete chat UI for React Native & Web
โจ Features
- ๐จ Fully Customizable - Override any component with your own implementation
- ๐ Composer Actions - Attach photos, files, or trigger custom actions
- โฉ๏ธ Reply to Messages - Swipe-to-reply with reply preview and message threading
- โฎ๏ธ Load Earlier Messages - Infinite scroll with pagination support
- ๐ Copy to Clipboard - Long-press messages to copy text
- ๐ Smart Link Parsing - Auto-detect URLs, emails, phone numbers, hashtags, mentions
- ๐ค Avatars - User initials or custom avatar images
- ๐ Localized Dates - Full i18n support via Day.js
- โจ๏ธ Keyboard Handling - Smart keyboard avoidance for all platforms
- ๐ฌ System Messages - Display system notifications in chat
- โก Quick Replies - Bot-style quick reply buttons
- โ๏ธ Typing Indicator - Show when users are typing
- โ
Message Status - Tick indicators for sent/delivered/read states
- โฌ๏ธ Scroll to Bottom - Quick navigation button
- ๐ Web Support - Works with react-native-web
- ๐ฑ Expo Support - Easy integration with Expo projects
- ๐ TypeScript - Complete TypeScript definitions included
ย ย ย ย
ย ย ย ย
๐ Table of Contents
๐ Requirements
| React Native | >= 0.70.0 |
| iOS | >= 13.4 |
| Android | API 21+ (Android 5.0) |
| Expo | SDK 50+ |
| TypeScript | >= 5.0 (optional) |
๐ฆ Installation
Expo Projects
npx expo install react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
Bare React Native Projects
Step 1: Install the packages
Using yarn:
yarn add react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
Using npm:
npm install --save react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
Step 2: Install iOS pods
npx pod-install
Step 3: Configure react-native-reanimated
Follow the react-native-reanimated installation guide to add the Babel plugin.
๐ Usage
Basic Example
import React, { useState, useCallback, useEffect } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import { useHeaderHeight } from '@react-navigation/elements'
export function Example() {
const [messages, setMessages] = useState([])
const headerHeight = useHeaderHeight()
useEffect(() => {
setMessages([
{
_id: 1,
text: 'Hello developer',
createdAt: new Date(),
user: {
_id: 2,
name: 'John Doe',
avatar: 'https://placeimg.com/140/140/any',
},
},
])
}, [])
const onSend = useCallback((messages = []) => {
setMessages(previousMessages =>
GiftedChat.append(previousMessages, messages),
)
}, [])
return (
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: 1,
}}
keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
/>
)
}
๐ก Tip: Check out more examples in the example directory including Slack-style messages, quick replies, and custom components.
๐ Data Structure
Messages, system messages, and quick replies follow the structure defined in Models.ts.
Message Object Structure
interface IMessage {
_id: string | number
text: string
createdAt: Date | number
user: User
image?: string
video?: string
audio?: string
system?: boolean
sent?: boolean
received?: boolean
pending?: boolean
quickReplies?: QuickReplies
}
interface User {
_id: string | number
name?: string
avatar?: string | number | (() => React.ReactNode)
}
๐ Props Reference
Core Configuration
messages (Array) - Messages to display
user (Object) - User sending the messages: { _id, name, avatar }
onSend (Function) - Callback when sending a message
messageIdGenerator (Function) - Generate an id for new messages. Defaults to a simple random string generator.
locale (String) - Locale to localize the dates. You need first to import the locale you need (ie. require('dayjs/locale/de') or import 'dayjs/locale/fr')
colorScheme ('light' | 'dark') - Force color scheme (light/dark mode). When set to 'light' or 'dark', it overrides the system color scheme. When undefined, it uses the system color scheme. Default is undefined.
Refs
messagesContainerRef (FlatList ref) - Ref to the flatlist
textInputRef (TextInput ref) - Ref to the text input
Keyboard & Layout
keyboardProviderProps (Object) - Props to be passed to the KeyboardProvider for keyboard handling. Default values:
statusBarTranslucent: true - Required on Android for correct keyboard height calculation when status bar is translucent (edge-to-edge mode)
navigationBarTranslucent: true - Required on Android for correct keyboard height calculation when navigation bar is translucent (edge-to-edge mode)
keyboardAvoidingViewProps (Object) - Props to be passed to the KeyboardAvoidingView. See keyboardVerticalOffset below for proper keyboard handling.
isAlignedTop (Boolean) Controls whether or not the message bubbles appear at the top of the chat (Default is false - bubbles align to bottom)
isInverted (Bool) - Reverses display order of messages; default is true
Understanding keyboardVerticalOffset
The keyboardVerticalOffset tells the KeyboardAvoidingView where its container starts relative to the top of the screen. This is essential when GiftedChat is not positioned at the very top of the screen (e.g., when you have a navigation header).
Default value: insets.top (status bar height from useSafeAreaInsets()). This works correctly only when GiftedChat fills the entire screen without a navigation header. If you have a navigation header, you need to pass the correct offset via keyboardAvoidingViewProps.
What the value means: The offset equals the distance (in points) from the top of the screen to the top of your GiftedChat container. This typically includes:
- Status bar height
- Navigation header height (on iOS,
useHeaderHeight() already includes status bar)
How to use:
import { useHeaderHeight } from '@react-navigation/elements'
function ChatScreen() {
const headerHeight = useHeaderHeight()
return (
<GiftedChat
keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
// ... other props
/>
)
}
Note: useHeaderHeight() requires your chat component to be rendered inside a proper navigation screen (not conditional rendering). If it returns 0, ensure your chat screen is a real navigation screen with a visible header.
Why this matters: Without the correct offset, the keyboard may overlap the input field or leave extra space. The KeyboardAvoidingView uses this value to calculate how much to shift the content when the keyboard appears.
Text Input & Composer
text (String) - Input text; default is undefined, but if specified, it will override GiftedChat's internal state. Useful for managing text state outside of GiftedChat (e.g. with Redux). Don't forget to implement textInputProps.onChangeText to update the text state.
initialText (String) - Initial text to display in the input field
isSendButtonAlwaysVisible (Bool) - Always show send button in input text composer; default false, show only when text input is not empty
isTextOptional (Bool) - Allow sending messages without text (useful for media-only messages); default false. Use with isSendButtonAlwaysVisible for media attachments.
minComposerHeight (Object) - Custom min-height of the composer.
maxComposerHeight (Object) - Custom max height of the composer.
minInputToolbarHeight (Integer) - Minimum height of the input toolbar; default is 44
renderInputToolbar (Component | Function) - Custom message composer container
renderComposer (Component | Function) - Custom text input message composer
renderSend (Component | Function) - Custom send button; you can pass children to the original Send component quite easily, for example, to use a custom icon (example)
renderActions (Component | Function) - Custom action button on the left of the message composer
renderAccessory (Component | Function) - Custom second line of actions below the message composer
textInputProps (Object) - props to be passed to the <TextInput>.
Actions & Action Sheet
onPressActionButton (Function) - Callback when the Action button is pressed (if set, the default actionSheet will not be used)
actionSheet (Function) - Custom action sheet interface for showing action options
actions (Array) - Custom action options for the input toolbar action button; array of objects with title (string) and action (function) properties
actionSheetOptionTintColor (String) - Tint color for action sheet options
Messages & Message Container
messagesContainerStyle (Object) - Custom style for the messages container
renderMessage (Component | Function) - Custom message container
renderLoading (Component | Function) - Render a loading view when initializing
renderChatEmpty (Component | Function) - Custom component to render in the ListView when messages are empty
renderChatFooter (Component | Function) - Custom component to render below the MessagesContainer (separate from the ListView)
listProps (Object) - Extra props to be passed to the messages <FlatList>. Supports all FlatList props including maintainVisibleContentPosition for keeping scroll position when new messages arrive (useful for AI chatbots).
Message Bubbles & Content
renderBubble (Component | Function(props: BubbleProps)) - Custom message bubble. Receives BubbleProps as parameter.
renderMessageText (Component | Function) - Custom message text
renderMessageImage (Component | Function) - Custom message image
renderMessageVideo (Component | Function) - Custom message video
renderMessageAudio (Component | Function) - Custom message audio
renderCustomView (Component | Function) - Custom view inside the bubble
isCustomViewBottom (Bool) - Determine whether renderCustomView is displayed before or after the text, image and video views; default is false
onPressMessage (Function(context, message)) - Callback when a message bubble is pressed
onLongPressMessage (Function(context, message)) - Callback when a message bubble is long-pressed; you can use this to show action sheets (e.g., copy, delete, reply)
imageProps (Object) - Extra props to be passed to the <Image> component created by the default renderMessageImage
imageStyle (Object) - Custom style for message images
videoProps (Object) - Extra props to be passed to the video component created by the required renderMessageVideo
messageTextProps (Object) - Extra props to be passed to the MessageText component. Useful for customizing link parsing behavior, text styles, and matchers. Supports the following props:
matchers - Custom matchers for linking message content (like URLs, phone numbers, hashtags, mentions)
linkStyle - Custom style for links
email - Enable/disable email parsing (default: true)
phone - Enable/disable phone number parsing (default: true)
url - Enable/disable URL parsing (default: true)
hashtag - Enable/disable hashtag parsing (default: false)
mention - Enable/disable mention parsing (default: false)
hashtagUrl - Base URL for hashtags (e.g., 'https://x.com/hashtag')
mentionUrl - Base URL for mentions (e.g., 'https://x.com')
stripPrefix - Strip 'http://' or 'https://' from URL display (default: false)
TextComponent - Custom Text component to use (e.g., from react-native-gesture-handler)
Example:
<GiftedChat
messageTextProps={{
phone: false,
matchers: [
{
type: 'phone',
pattern: /\+?[1-9][0-9\-\(\) ]{7,}[0-9]/g,
getLinkUrl: (replacerArgs: ReplacerArgs): string => {
return replacerArgs[0].replace(/[\-\(\) ]/g, '')
},
getLinkText: (replacerArgs: ReplacerArgs): string => {
return replacerArgs[0]
},
style: styles.linkStyle,
onPress: (match: CustomMatch) => {
const url = match.getAnchorHref()
const options: {
title: string
action?: () => void
}[] = [
{ title: 'Copy', action: () => setStringAsync(url) },
{ title: 'Call', action: () => Linking.openURL(`tel:${url}`) },
{ title: 'Send SMS', action: () => Linking.openURL(`sms:${url}`) },
{ title: 'Cancel' },
]
showActionSheetWithOptions({
options: options.map(o => o.title),
cancelButtonIndex: options.length - 1,
}, (buttonIndex?: number) => {
if (buttonIndex === undefined)
return
const option = options[buttonIndex]
option.action?.()
})
},
},
],
linkStyle: { left: { color: 'blue' }, right: { color: 'lightblue' } },
}}
/>
See full example in LinksExample
Avatars
renderAvatar (Component | Function) - Custom message avatar; set to null to not render any avatar for the message
isUserAvatarVisible (Bool) - Whether to render an avatar for the current user; default is false, only show avatars for other users
isAvatarVisibleForEveryMessage (Bool) - When false, avatars will only be displayed when a consecutive message is from the same user on the same day; default is false
onPressAvatar (Function(user)) - Callback when a message avatar is tapped
onLongPressAvatar (Function(user)) - Callback when a message avatar is long-pressed
isAvatarOnTop (Bool) - Render the message avatar at the top of consecutive messages, rather than the bottom; default is false
Username
isUsernameVisible (Bool) - Indicate whether to show the user's username inside the message bubble; default is false
renderUsername (Component | Function) - Custom Username container
Date & Time
timeFormat (String) - Format to use for rendering times; default is 'LT' (see Day.js Format)
dateFormat (String) - Format to use for rendering dates; default is 'D MMMM' (see Day.js Format)
dateFormatCalendar (Object) - Format to use for rendering relative times; default is { sameDay: '[Today]' } (see Day.js Calendar)
renderDay (Component | Function) - Custom day above a message
dayProps (Object) - Props to pass to the Day component:
containerStyle - Custom style for the day container
wrapperStyle - Custom style for the day wrapper
textProps - Props to pass to the Text component (e.g., style, allowFontScaling, numberOfLines)
renderTime (Component | Function) - Custom time inside a message
timeTextStyle (Object) - Custom text style for time inside messages (supports left/right styles)
isDayAnimationEnabled (Bool) - Enable animated day label that appears on scroll; default is true
System Messages
renderSystemMessage (Component | Function) - Custom system message
Load Earlier Messages
loadEarlierMessagesProps (Object) - Props to pass to the LoadEarlierMessages component. The button is only visible when isAvailable is true. Supports the following props:
isAvailable - Controls button visibility (default: false)
onPress - Callback when button is pressed
isLoading - Display loading indicator (default: false)
isInfiniteScrollEnabled - Enable infinite scroll up when reaching the top of messages container, automatically calls onPress (not yet supported for web)
label - Override the default "Load earlier messages" text
containerStyle - Custom style for the button container
wrapperStyle - Custom style for the button wrapper
textStyle - Custom style for the button text
activityIndicatorStyle - Custom style for the loading indicator
activityIndicatorColor - Color of the loading indicator (default: 'white')
activityIndicatorSize - Size of the loading indicator (default: 'small')
renderLoadEarlier (Component | Function) - Custom "Load earlier messages" button
Typing Indicator
isTyping (Bool) - Typing Indicator state; default false. If you userenderFooter it will override this.
renderTypingIndicator (Component | Function) - Custom typing indicator component
typingIndicatorStyle (StyleProp) - Custom style for the TypingIndicator component.
renderFooter (Component | Function) - Custom footer component on the ListView, e.g. 'User is typing...'; see CustomizedFeaturesExample.tsx for an example. Overrides default typing indicator that triggers when isTyping is true.
Quick Replies
See Quick Replies example in messages.ts
onQuickReply (Function) - Callback when sending a quick reply (to backend server)
renderQuickReplies (Function) - Custom all quick reply view
quickReplyStyle (StyleProp) - Custom quick reply view style
quickReplyTextStyle (StyleProp) - Custom text style for quick reply buttons
quickReplyContainerStyle (StyleProp) - Custom container style for quick replies
renderQuickReplySend (Function) - Custom quick reply send view
Reply to Messages
Gifted Chat supports swipe-to-reply functionality out of the box. When enabled, users can swipe on a message to reply to it, displaying a reply preview in the input toolbar and the replied message above the new message bubble.
Note: This feature uses ReanimatedSwipeable from react-native-gesture-handler and react-native-reanimated for smooth, performant animations.
Basic Usage
<GiftedChat
messages={messages}
onSend={onSend}
user={{ _id: 1 }}
reply={{
swipe: {
isEnabled: true,
direction: 'left',
},
}}
/>
Reply Props (Grouped)
The reply prop accepts an object with the following structure:
interface ReplyProps<TMessage> {
swipe?: {
isEnabled?: boolean
direction?: 'left' | 'right'
onSwipe?: (message: TMessage) => void
renderAction?: (
progress: SharedValue<number>,
translation: SharedValue<number>,
position: 'left' | 'right'
) => React.ReactNode
actionContainerStyle?: StyleProp<ViewStyle>
}
previewStyle?: {
containerStyle?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
imageStyle?: StyleProp<ImageStyle>
}
messageStyle?: {
containerStyle?: StyleProp<ViewStyle>
containerStyleLeft?: StyleProp<ViewStyle>
containerStyleRight?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
textStyleLeft?: StyleProp<TextStyle>
textStyleRight?: StyleProp<TextStyle>
imageStyle?: StyleProp<ImageStyle>
}
message?: ReplyMessage
onClear?: () => void
onPress?: (message: TMessage) => void
renderPreview?: (props: ReplyPreviewProps) => React.ReactNode
renderMessageReply?: (props: MessageReplyProps) => React.ReactNode
}
ReplyMessage Structure
When a message has a reply, it includes a replyMessage property:
interface ReplyMessage {
_id: string | number
text: string
user: User
image?: string
audio?: string
}
Advanced Example with External State
const [replyMessage, setReplyMessage] = useState<ReplyMessage | null>(null)
<GiftedChat
messages={messages}
onSend={messages => {
const newMessages = messages.map(msg => ({
...msg,
replyMessage: replyMessage || undefined,
}))
setMessages(prev => GiftedChat.append(prev, newMessages))
setReplyMessage(null)
}}
user={{ _id: 1 }}
reply={{
swipe: {
isEnabled: true,
direction: 'right',
onSwipe: setReplyMessage,
},
message: replyMessage,
onClear: () => setReplyMessage(null),
onPress: (msg) => scrollToMessage(msg._id),
}}
/>
Smooth Animations
The reply preview automatically animates when:
- Appearing: Smoothly expands from zero height with fade-in effect
- Disappearing: Smoothly collapses with fade-out effect
- Content changes: Smoothly transitions when replying to a different message
These animations use react-native-reanimated for 60fps performance.
Scroll to Bottom
isScrollToBottomEnabled (Bool) - Enables the scroll to bottom Component (Default is false)
scrollToBottomComponent (Function) - Custom Scroll To Bottom Component container
scrollToBottomOffset (Integer) - Custom Height Offset upon which to begin showing Scroll To Bottom Component (Default is 200)
scrollToBottomStyle (Object) - Custom style for Scroll To Bottom wrapper (position, bottom, right, etc.)
scrollToBottomContentStyle (Object) - Custom style for Scroll To Bottom content (size, background, shadow, etc.)
Maintaining Scroll Position (AI Chatbots)
For AI chat interfaces where long responses arrive and you don't want to disrupt the user's reading position, use maintainVisibleContentPosition via listProps:
<GiftedChat
listProps={{
maintainVisibleContentPosition: {
minIndexForVisible: 0,
},
}}
/>
<GiftedChat
listProps={{
maintainVisibleContentPosition: {
minIndexForVisible: 0,
autoscrollToTopThreshold: 10,
},
}}
/>
const [isScrolledUp, setIsScrolledUp] = useState(false)
<GiftedChat
listProps={{
onScroll: (event) => {
setIsScrolledUp(event.contentOffset.y > 50)
},
maintainVisibleContentPosition: isScrolledUp
? { minIndexForVisible: 0, autoscrollToTopThreshold: 10 }
: undefined,
}}
/>
๐ฑ Platform Notes
Android
Keyboard configuration
If you are using Create React Native App / Expo, no Android specific installation steps are required. Otherwise, we recommend modifying your project configuration:
Make sure you have android:windowSoftInputMode="adjustResize" in your AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
For Expo, you can append KeyboardAvoidingView after GiftedChat (Android only):
<View style={{ flex: 1 }}>
<GiftedChat />
{Platform.OS === 'android' && <KeyboardAvoidingView behavior="padding" />}
</View>
Web (react-native-web)
With create-react-app
- Install react-app-rewired:
yarn add -D react-app-rewired
- Create
config-overrides.js:
module.exports = function override(config, env) {
config.module.rules.push({
test: /\.js$/,
exclude: /node_modules[/\\](?!react-native-gifted-chat)/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
configFile: false,
presets: [
['@babel/preset-env', { useBuiltIns: 'usage' }],
'@babel/preset-react',
],
plugins: ['@babel/plugin-proposal-class-properties'],
},
},
})
return config
}
Examples:
๐งช Testing
Triggering layout events in tests
TEST_ID is exported as constants that can be used in your testing library of choice.
Gifted Chat uses onLayout to determine the height of the chat container. To trigger onLayout during your tests:
const WIDTH = 200
const HEIGHT = 2000
const loadingWrapper = getByTestId(TEST_ID.LOADING_WRAPPER)
fireEvent(loadingWrapper, 'layout', {
nativeEvent: {
layout: {
width: WIDTH,
height: HEIGHT,
},
},
})
๐ฆ Example App
The repository includes a comprehensive example app demonstrating all features:
git clone https://github.com/FaridSafi/react-native-gifted-chat.git
cd react-native-gifted-chat/example
yarn install
npx expo run:ios
npx expo run:android
npx expo start --web
The example app showcases:
- ๐ฌ Basic chat functionality
- ๐จ Custom message bubbles and avatars
- โฉ๏ธ Reply to messages with swipe gesture
- โก Quick replies (bot-style)
- โ๏ธ Typing indicators
- ๐ Attachment actions
- ๐ Link parsing and custom matchers
- ๐ Web compatibility
โ Troubleshooting
TextInput is hidden on Android
Make sure you have android:windowSoftInputMode="adjustResize" in your AndroidManifest.xml. See Android configuration above.
How to set Bubble color for each user?
See this issue for examples.
How to customize InputToolbar styles?
See this issue for examples.
How to manually dismiss the keyboard?
See this issue for examples.
How to use renderLoading?
See this issue for examples.
๐ค Have a Question?
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Install dependencies (
yarn install)
- Make your changes
- Run tests (
yarn test)
- Run linting (
yarn lint)
- Build the library (
yarn build)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
Development Setup
yarn install
yarn build
yarn test
yarn lint
yarn prepublishOnly
๐ฅ Authors
Original Author: Farid Safi
Co-maintainer: Xavier Carpentier - Hire Xavier
Maintainer: Kesha Antonov
Please note that this project is maintained in free time. If you find it helpful, please consider becoming a sponsor.
๐ License
MIT
Built with โค๏ธ by the React Native community