Sigma Stack
3 min read

Building a React Native AI Chat App UI (Expo + TypeScript)

By Arslan Shaukat

React NativeAIExpoUI

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 Message model first; the UI follows from it.
  • Use an inverted FlatList for 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 templates

More

Keep reading