Typing IndicatoriOS
iMessage-style three-dot bounce for chat threads — phase-staggered wave, optional bubble surface, reduced-motion opacity pulse
$ npx lumioui add typing-indicator
import { TypingIndicator } from "@/components/ui/typing-indicator/typing-indicator";
Files installed into your repo
components/ui/typing-indicator/typing-indicator.tsx
Native packages required
@lumioui/core@latestreact-native-reanimated@^4.0.0
Props7
| visible? | boolean | Show the dots. Animates in/out rather than hard-mounting. Default true. |
| bubble? | boolean | Wrap the dots in a received-message bubble surface. Default false. |
| dotSize? | number | Dot diameter in px. Default 7. |
| speed? | number | Seconds per wave cycle. Default 1. |
| color? | "primary" | "foreground" | "mutedForeground" | Dot color token. Default "mutedForeground". |
| accessibilityLabel? | string | Screen-reader announcement. Default "Typing…". |
| className? | string | |
Typing Indicatorthread.tsx
Live preview offline the relay is unreachable — check back shortly
typing-indicator/variants/thread.tsx open iMessage-style three-dot bounce for chat threads — phase-staggered wave, optional bubble surface, reduced-motion opacity pulse
import { TypingThread } from "@/components/ui/typing-indicator/variants/thread";
export function Example() {
return (
<TypingThread />
);
}
import * as React from "react";
import { View } from "react-native";
import { TypingIndicator } from "../typing-indicator";
import { Avatar } from "../../avatar/avatar";
import { Button } from "../../button/button";
import { Text } from "../../text/text";
import { Glass } from "../../glass/glass";
/** TypingThread — chat card where Priya's typing bubble appears after send. */
export function TypingThread() {
const [sent, setSent] = React.useState(false);
const [typing, setTyping] = React.useState(false);
const send = () => {
setSent(true);
setTyping(true);
setTimeout(() => setTyping(false), 4000);
};
return (
<View className="w-full gap-3">
<Glass interactive curvature="edge" className="w-full gap-3 rounded-2xl p-4">
<View className="flex-row items-center gap-2.5">
<Avatar size="sm" source={{ uri: "https://i.pravatar.cc/150?img=12" }} fallback="PN" alt="Priya Nair" />
<View className="flex-1">
<Text variant="small" className="font-semibold">Priya Nair</Text>
<Text variant="muted" className="text-xs">{typing ? "typing…" : "Active now"}</Text>
</View>
</View>
<View className="self-start max-w-[85%] rounded-2xl rounded-bl-sm bg-muted px-3.5 py-2.5">
<Text variant="small">Landing page copy is ready for review!</Text>
</View>
{sent && (
<View className="self-end max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-3.5 py-2.5">
<Text variant="small" className="text-primary-foreground">Perfect — shipping it today 🚢</Text>
</View>
)}
{typing && (
<View className="flex-row items-end gap-2">
<Avatar size="sm" source={{ uri: "https://i.pravatar.cc/150?img=12" }} fallback="PN" alt="Priya Nair" />
<TypingIndicator bubble />
</View>
)}
{!sent ? (
<Button size="sm" variant="glass" onPress={send}>Send reply</Button>
) : typing ? null : (
<Text variant="muted" className="text-xs text-center">Priya stopped typing — 3.5s ago</Text>
)}
</Glass>
</View>
);
}