Building a React Native AI Chat App UI (Expo + TypeScript)
By Arslan Shaukat
Everyone wants to ship an AI app right now, and the model is the easy part — a single fetch to an API. What actually makes a chat app feel premium is the UI: smooth bubbles, a believable typing indicator, an input bar that behaves, and theming that doesn't look like a default template. Here's how I structure it.
Model the conversation first
Before any UI, type the data. A message is small but worth getting right:
type Role = "user" | "assistant";
interface Message {
id: string;
role: Role;
text: string;
createdAt: number;
}Everything else — bubbles, list, scrolling — is just a function of an array of these.
The chat list
Use a FlatList inverted so new messages appear at the bottom and the list naturally starts scrolled to the latest:
<FlatList
data={[...messages].reverse()}
inverted
keyExtractor={(m) => m.id}
renderItem={({ item }) => <ChatBubble message={item} />}
/>inverted saves you from manually scrolling to the end on every new token.
A bubble that reads at a glance
The whole point of a chat UI is that you can tell who said what without thinking. Align by role and flip the colors:
function ChatBubble({ message }: { message: Message }) {
const isUser = message.role === "user";
return (
<View style={[styles.row, isUser && styles.rowUser]}>
<View style={[styles.bubble, isUser ? styles.user : styles.ai]}>
<Text style={styles.text}>{message.text}</Text>
</View>
</View>
);
}Keep the radius soft and give the two roles distinct backgrounds — that contrast is what makes it instantly readable.
The typing indicator
This one detail is what makes the app feel alive. Three dots with a staggered opacity loop is enough:
function TypingDots() {
// animate three dots' opacity on a staggered loop
return (
<View style={styles.typing}>
<Dot delay={0} />
<Dot delay={150} />
<Dot delay={300} />
</View>
);
}Show it the moment you send a request and remove it when the first token arrives.
Connecting to a model
Keep the network layer in one place so the UI never cares which provider you use:
async function sendMessage(history: Message[]): Promise<string> {
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages: history }),
});
const data = await res.json();
return data.reply;
}Because the UI only depends on the Message type, you can swap models, add streaming, or change providers without touching a single component.
Theming so it doesn't look generic
Pull colors, radii, and shadows from a token set and support light/dark from the start. The difference between a "default RN app" look and a premium one is almost entirely soft shadows, consistent radii, and a considered palette — not more features.
If you'd rather start from a finished version of all of this — onboarding, chat, voice, and settings screens with light/dark theming, ready to connect to your model — that's exactly what my AI Chat App UI Kit gives you.
Wrap-up
- Type the
Messagemodel first; the UI follows from it. - Use an
invertedFlatListfor natural chat scrolling. - Align bubbles by role and make the contrast obvious.
- A typing indicator is the cheapest way to make it feel real.
- Keep the model behind one function so the UI is provider-agnostic.
Skip the boilerplate
Production-ready React Native starters and UI kits — buy once, clone, and start on the feature that matters.
Browse the templatesMore