by rio

A curated collection of production-ready Expo examples covering AI chatbots, authentication, storage, GraphQL, maps, animations, and more—built for iOS, Android, and Web.
This block wires an AI-powered streaming chat interface into an Expo Router project using the Vercel AI SDK, NativeWind (Tailwind CSS for React Native), and a server-side API route. It targets developers building cross-platform (iOS, Android, web) AI chat features inside an Expo Router app.
src/app/_layout.tsx - Root layout that bootstraps fonts, theming, and global CSS importsrc/app/index.tsx - Entry screen that renders the <Chat /> componentsrc/app/api/chat+api.ts - Expo Router API route handling streaming AI completions server-sidesrc/components/chat.tsx - Core chat UI: input bar, message list, streaming state managementsrc/components/keyboard-padding.tsx - Utility component that adjusts scroll view for software keyboardsrc/components/tool-cards.tsx - Renders structured tool-call result cards inside the conversationsrc/components/user-message.tsx - Renders a single user bubble in the message threadsrc/lib/utils.ts - Shared helper utilities (e.g., cn class-name merger)src/utils/fetch-polyfill.ts - Native fetch polyfill for streaming responses on iOS/Androidsrc/utils/fetch-polyfill.web.ts - No-op polyfill override for web platformsrc/global.css - Tailwind/NativeWind base stylesheet entry pointtailwind.config.js - NativeWind-aware Tailwind configurationmetro.config.js - Metro bundler config with NativeWind integrationbabel.config.js - Babel preset wiring nativewind JSX transformnativewind-env.d.ts - Auto-generated NativeWind type referenceapp.json - Expo application manifesttsconfig.json - TypeScript project configglobal.d.ts - Ambient type declarationsnpm install ai @ai-sdk/openai
npm install nativewind tailwindcss
npm install expo-router expo expo-status-bar
npm install react-native-safe-area-context react-native-screens
npm install react-native-reanimated
After installing, run native build steps if targeting iOS or Android:
# If using bare workflow or after adding native modules:
npx expo prebuild
# iOS
npx pod-install
Spin up an isolated sandbox and run it server-side — no local setup.
Tetrees AI Review of this version
This React, React Native mobile app completed archive review. Structure, dependency manifests, documentation, functional source, and common risk patterns were checked by the Tetrees verification pipeline; runtime phases are stated separately.
Deterministic AVCP artifact review
Pipeline avcp-2026-08-04.1 · SHA-256 3835994ca2a5d578…
This version-scoped review deterministically inspects the submitted archive for structure, dependencies, documentation, functional source, and common malicious or high-risk signals. Build and test phases are reported as passed only after an isolated sandbox audition. It is not a guarantee of perfect security.
Reviewed Aug 4, 2026
Push this product straight into your AI IDE, web builder or cloud IDE.
Connect Tetrees to a compatible AI IDE, list products you own, and request the verified ZIP without exposing seller upload controls.
No reviews yet.
Sign in to join the discussion
Loading discussion…
Set your OpenAI API key as an environment variable accessible to the server route:
OPENAI_API_KEY=sk-...
For Expo Go / EAS builds, add it to .env.local and expose it server-side only (never embed in the client bundle).
Copy source files. Paste the src/ directory into your project root. Adjust the alias @/ in your imports if your project uses a different src layout.
Configure path alias. Ensure tsconfig.json maps @/* to src/*:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
metro.config.js with:const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./src/global.css" });
babel.config.js:module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
Wire Tailwind. Ensure tailwind.config.js points at your source and includes the NativeWind preset (as shown in the provided file).
Import global CSS. In your root layout (src/app/_layout.tsx) add:
import "@/global.css";
OPENAI_API_KEY. Add it to .env.local. The API route at src/app/api/chat+api.ts reads it server-side via process.env.OPENAI_API_KEY.src/app/index.tsx)export default function Page(): JSX.Element
The Expo Router screen component for the / route. Renders <Chat /> directly. Drop this file into your app/ directory to register the route, or call <Chat /> from any existing screen.
src/components/chat.tsx)import { Chat } from "@/components/chat";
// usage:
<Chat />
The stateful chat container. Manages message history, calls the /api/chat streaming endpoint, and composes all sub-components (UserMessage, ToolCards, KeyboardPadding). Use this as the single mount point for the entire AI chat experience.
metro.config.js usage)withNativeWind(config: MetroConfig, options: { input: string }): MetroConfig
Metro transform that enables NativeWind's CSS-to-JS compilation. The input path must point to your global.css that contains @tailwind directives. Called once in metro.config.js; no runtime usage needed.
src/lib/utils.ts)import { cn } from "@/lib/utils";
cn(...classes: ClassValue[]): string
Class-name merger (wraps clsx + tailwind-merge). Use it anywhere you conditionally compose Tailwind class strings in components, keeping NativeWind-compatible output.
Drop the chat view into any existing Expo Router screen without touching the API route.
// src/app/(tabs)/assistant.tsx
import { Chat } from "@/components/chat";
import { SafeAreaView } from "react-native-safe-area-context";
export default function AssistantTab() {
return (
<SafeAreaView className="flex-1 bg-white">
<Chat />
</SafeAreaView>
);
}
Render UserMessage independently inside a custom list renderer.
// src/components/custom-thread.tsx
import { UserMessage } from "@/components/user-message";
import { ScrollView } from "react-native";
const messages = [
{ id: "1", content: "Hello, what can you do?" },
{ id: "2", content: "Show me the weather in Berlin." },
];
export function CustomThread() {
return (
<ScrollView className="flex-1 p-4">
{messages.map((msg) => (
<UserMessage key={msg.id} content={msg.content} />
))}
</ScrollView>
);
}
Modify the server-side API route to swap the model or add a system prompt.
// src/app/api/chat+api.ts (extended version)
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: openai("gpt-4o"),
system: "You are a concise assistant. Reply in plain text only.",
messages,
maxTokens: 512,
});
return result.toDataStreamResponse();
}
If you add another file that relies on streaming fetch on native, import the polyfill at the top:
// src/utils/my-streaming-client.ts
import "@/utils/fetch-polyfill";
export async function streamFromCustomEndpoint(prompt: string) {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: [{ role: "user", content: prompt }] }),
});
return res.body; // ReadableStream available after polyfill
}
src/app/index.tsx - Single-route entry screen; delegates all UI to <Chat />.src/app/_layout.tsx - Expo Router root layout; imports global CSS, provides safe-area and navigation context.src/app/api/chat+api.ts - Server function (Expo Router API route) that proxies messages to the OpenAI streaming endpoint via the AI SDK.src/components/chat.tsx - Stateful chat container; owns message list, input field, and streaming lifecycle.src/components/keyboard-padding.tsx - Listens to Keyboard events and insets scroll content so the input is never hidden.src/components/tool-cards.tsx - Renders AI tool-call results (structured data) as card UI elements.src/components/user-message.tsx - Presentational bubble for outgoing user messages.src/lib/utils.ts - cn helper combining clsx and tailwind-merge for safe class composition.src/utils/fetch-polyfill.ts - Patches global fetch on React Native to support ReadableStream bodies needed by the AI SDK.src/utils/fetch-polyfill.web.ts - Platform override that skips the polyfill on web where native fetch is sufficient.src/global.css - NativeWind entry stylesheet with @tailwind base/components/utilities directives.tailwind.config.js - Tailwind config scoped to src/**, using NativeWind preset and hoverOnlyWhenSupported.metro.config.js - Metro config with withNativeWind wrapper pointing at global.css.babel.config.js - Expo Babel preset with jsxImportSource: "nativewind" and nativewind/babel plugin.nativewind-env.d.ts - Auto-generated; provides TypeScript types for className prop on RN components.app.json - Expo project manifest (name, slug, SDK version, plugins).tsconfig.json - TypeScript config with path aliases and Expo preset extension.global.d.ts - Ambient declarations for non-TS assets or global augmentations.@/utils/fetch-polyfill before any AI SDK call in native entry points; the polyfill patches fetch to handle ReadableStream.className prop not recognized by TypeScript. Ensure nativewind-env.d.ts is included in tsconfig.json include array and never manually edited.metro.config.js uses withNativeWind with the correct input path; a wrong path silently produces unstyled output.OPENAI_API_KEY leaks to the client bundle. Only read it inside src/app/api/ server files; never import it in component files. Expo Router API routes run server-side only.tailwind.config.js. Run npx expo start --clear to force a full cache invalidation.babel-preset-expo version mismatch with NativeWind. Pin nativewind and babel-preset-expo to mutually compatible versions; check the NativeWind release notes for the exact pairing.I have an Expo Router project. I want to integrate the `with-router-ai` source block into it.
Context files available:
- source/ (the full block, as described in USAGE.md)
- USAGE.md (integration guide)
Please help me do the following step by step:
1. Copy the files from source/src/ into my project's src/ directory, resolving any naming conflicts with my existing files.
2. Merge metro.config.js, babel.config.js, tailwind.config.js, and tsconfig.json changes into my existing config files without overwriting my current settings.
3. Add all required npm dependencies listed in USAGE.md ## Required dependencies.
4. Wire the OPENAI_API_KEY environment variable so it is only accessible server-side in the API route.
5. Register the /api/chat route and the root Chat screen in my existing Expo Router layout.
6. Verify that the NativeWind fetch polyfill is imported before any AI SDK usage in native entry points.
7. Run a quick sanity check: show me what the final src/app/_layout.tsx and metro.config.js should look like in my project.
My existing project structure is: [PASTE YOUR FILE TREE HERE]
My existing package.json dependencies are: [PASTE HERE]
See source/LICENSE if present. This example originates from the official Expo Examples repository, maintained by the Expo team. Refer to that repository for the upstream license (typically MIT) and the latest source.
This product’s unique contribution is clean room implementation of valuable code block into AVCP compatible standard.
The full install guide and integration prompts unlock after purchase.
Mobile App Templates & App Source Code
Free