What's new vs Gifted Chat
Kept in sync with upstream react-native-gifted-chat's latest master, so you keep everything it already has - and the same IMessage model and prop names. Everything added here is non-breaking and opt-in; the media and recording features only activate once you install their optional peer deps.
Side-by-side comparison
| Added in this fork | react-native-gifted-chat | @kesha-antonov/react-native-chat |
|---|---|---|
| Active maintenance | 💤 sporadic | ✅ active |
| Modern default UI (modern messenger look) | dated 2020 look | ✅ wallpaper, tails, inline time, fully overridable |
| Light/Dark theme system | per-component color props | ✅ theme / darkTheme tokens, runtime switch |
| Streaming (AI) messages | ❌ | ✅ token-by-token + typing cursor |
| Emoji reactions | ❌ | ✅ long-press picker + reaction pills |
| Emoji keyboard | ❌ | ✅ built in: tabs, search, recents, skin tones |
| Attachment menu | ❌ (actions sheet) | ✅ built in: photo grid + caption, files, location |
| File messages | ❌ | ✅ IMessage.file document bubble |
| Swipe-to-reply + reply preview | ❌ | ✅ built in |
| Video / audio messages | "not implemented, render your own" | ✅ inline players + tappable fallback |
| Voice notes (hold-to-record + waveform) | ❌ | ✅ optional |
| Video messages (round camera notes) | ❌ | ✅ optional |
| Location messages | ❌ ignored | ✅ map card → opens system maps |
| Bubble tails, inline time + run grouping | flat bubbles | ✅ |
Theming in one line
Override any token, light and dark
// Modern defaults out of the box, or override any token (light + dark):
<Chat
theme={{ colors: { accent: '#3390EC', outgoingBubble: '#EFFEDE' } }}
darkTheme={{ colors: { background: '#0E1621' } }}
{...props}
/>
Voice, video and location
Recording and media playback
Voice notes (expo-audio) and round video notes (react-native-vision-camera) turn on by themselves once their peers are installed: the mic button records on hold, and a tap switches it to the camera. Pass false to turn either off.
<Chat
videoRecording={{ maxDuration: 30 }} // optional: configure; `false` turns round notes off
mediaPlayback={{ playbackRate: 1.5 }} // optional: start notes at 1.5x, auto-play the next note
// location messages render automatically for any IMessage with a `location`
{...props}
/>
# Optional inline media playback + recording:
npx expo install expo-video expo-audio expo-image-picker
# Optional: round video notes and the voice-note waveform
# (vision-camera 5+, which runs on Nitro and needs its two Nitro packages too)
npx expo install react-native-vision-camera react-native-nitro-modules react-native-nitro-image react-native-audio-api react-native-svg expo-haptics
# Optional: trim paused voice takes, blur behind the round-video camera
npx expo install expo-file-system expo-blur
Recording and playback work like this:
- Hold the record button to record, release to send. With both voice and video enabled, a tap switches it between mic and camera, and the choice is remembered. With only video, the camera sits in the record button's place.
- Slide left to cancel: all the way across cancels at once; releasing past about half the way cancels too. Slide up to lock and keep recording hands-free. The two exclude each other: no lock once the cancel slide is under way. "Slide up to lock" is shown for the first few takes.
- Locked, the bar shows a
m:ss,ttimer and a CANCEL button, and the lock turns into a pause button. - A paused voice take turns into a preview: its waveform with play, to send or delete. The mic above send resumes recording onto its end, and the handles at either end of the waveform trim it. Resuming needs
react-native-audio-api; trimming also needsexpo-file-system, and a trimmed take is sent as a WAV. - A paused round video take holds the recording open: the circle shows the frame it paused on (Android; iOS keeps the live camera, dimmed), the bar offers delete and send, and the camera above send resumes into the same video. Round takes can't be trimmed.
- A round note records on the first hold, with the camera drawn over a blurred chat (when
expo-bluris installed). Switch camera and flash work while recording (the screen lights up for the front camera, the torch for the back one), and you can pinch to zoom. At its 60s limit the take stops into a looping preview (tap to hear it) instead of sending. - Haptic ticks on start, stop, lock and mode switch when
expo-hapticsis installed. - Starting a recording pauses any note that is playing.
- Only one note plays at a time. When a voice or round video note plays to its end, the next newer one in the chat starts.
- While a note plays or is paused, a "now playing" bar at the top of the chat has play/pause, the sender, the speed (1x / 1.5x / 2x; long-press for more, shared by every note) and close; a tap on the title scrolls to the note.
- Round notes rest in the list on their first frame with a play button (or loop silently with
isRoundVideoAutoplayEnabled). A tap plays one from the start with sound and grows it, hiding the sender's avatar; a tap pauses it, and a paused note can be scrubbed by dragging around its ring. Its length, in a pill at its bottom-left, counts down while it plays, and playing bars replace its unread dot once it is heard. - Set
isMediaUnreadon a message for the unread dot;mediaPlayback.onMediaReadreports when an incoming one is played.
Emoji keyboard and attachment menu
Built in, on by default
The composer's emoji button opens the built-in emoji keyboard in the system keyboard's place - at the last keyboard height, so switching between the two keeps the composer still. It has the category tabs, search (scroll the grid up to reveal it), recents by use count, a skin-tone popup on long-press and a repeating backspace. A tap inserts at the caret whether or not you control text.
The paperclip opens the built-in attachment menu: your newest photos in a 3-column grid (with expo-media-library), numbered as you pick them, a caption and a "Send N" button; File and Location tabs; a camera tile. Picks arrive in onSend as ordinary messages - image, video, file ({ uri, name, size?, mimeType? }) or location - so there is nothing to wire.
# Each tab shows only when its package is installed:
npx expo install expo-media-library expo-image-picker expo-document-picker expo-location
# Optional: keep emoji recents across launches, share received files, fast ph:// thumbnails
npx expo install @react-native-async-storage/async-storage expo-sharing expo-image
<Chat
emojiKeyboard={{ columns: 8, recents: { storageKey: 'my-app/emoji' } }}
attachments={{
tabs: ['gallery', 'file'],
// Upload first, then send what your backend returns (or `null` to send nothing).
onPick: async (picked, { caption }) => upload(picked, caption),
}}
{...props}
/>
Hide either with emojiKeyboard={false} / attachments={false}. onPressEmoji replaces the emoji keyboard and actions / onPressActionButton / renderActions replace the attachment menu, as before.
Permissions. iOS kills an app that asks for a permission without a usage string. In Expo, list the plugins with your own strings (the defaults are generic):
"plugins": [
["expo-image-picker", { "photosPermission": "...", "cameraPermission": "..." }],
["expo-media-library", { "photosPermission": "...", "savePhotosPermission": "...", "granularPermissions": ["photo", "video"] }],
["expo-location", { "locationWhenInUsePermission": "..." }],
"expo-document-picker",
"expo-sharing"
]
In a bare app add the keys yourself, then rebuild the native app:
| Package | iOS Info.plist | Android manifest |
|---|---|---|
expo-media-library | NSPhotoLibraryUsageDescription (NSPhotoLibraryAddUsageDescription to save) | READ_MEDIA_IMAGES, READ_MEDIA_VIDEO (READ_EXTERNAL_STORAGE below Android 13) |
expo-image-picker | NSPhotoLibraryUsageDescription, NSCameraUsageDescription (NSMicrophoneUsageDescription for video) | CAMERA (and RECORD_AUDIO for video) |
expo-location | NSLocationWhenInUseUsageDescription | ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION |
expo-document-picker | none (iCloud documents need the iCloud entitlement) | none |
expo-sharing | none | none |
Custom icons (e.g. Lucide)
Override any built-in icon
Built-in icons are the official Lucide glyphs, rendered via the optional react-native-svg peer when it is installed, or drawn with Views (no dependency) otherwise. Override any of them via the icons prop - the built-in icon is used for anything you don't override:
import { Send, Mic } from 'lucide-react-native'
<Chat
icons={{
send: ({ color, size }) => <Send color={color} size={size} />,
mic: ({ color, size }) => <Mic color={color} size={size} />,
}}
{...props}
/>
Overridable names: send, mic, camera, play, pause, check, checkAll, clock, pin, plus, close, chevronLeft, chevronDown, emoji, paperclip, reply, pencil, lock, trash.
Support This Project
I maintain this project in my free time with no compensation. If you find it useful and want to help keep it alive, please consider sponsoring. Your support means a lot! 💖
Using an AI coding agent?
llms.txtis a condensed integration guide for agents adding this library to an app, andAGENTS.mdcovers working inside this repository.