Skip to content
Framework: Vite + React + TypeScriptDifficulty: beginner

Mobile app shell

A phone-first layout with a sticky top bar, four tab screens, and a bottom navigation bar.

Designed at 360–430px first and framed as a phone on larger screens. Includes a bottom tab bar with aria-current, safe-area insets for installed web apps, and four example screens (home cards, a search list, an activity feed and a profile with accessible switches). No router dependency — tabs are component state.

  • react
  • mobile
  • pwa
  • bottom-nav
  • tailwind

This preview is the template's actual file tree and source code — exactly what is copied into a new project. Codexa does not publish screenshots or mock-ups of templates; run the project to see it rendered.

Files13 files

src/App.tsx44 lines
import { useState } from "react";
import { Home } from "./screens/Home";
import { Search } from "./screens/Search";
import { Activity } from "./screens/Activity";
import { Profile } from "./screens/Profile";

const TABS = [
  { id: "home", label: "Home", icon: "⌂" },
  { id: "search", label: "Search", icon: "⌕" },
  { id: "activity", label: "Activity", icon: "◷" },
  { id: "profile", label: "Profile", icon: "◯" },
] as const;
type TabId = (typeof TABS)[number]["id"];

export default function App() {
  const [tab, setTab] = useState<TabId>("home");
  const title = TABS.find((t) => t.id === tab)?.label ?? "";
  return (
    <div className="mx-auto flex min-h-screen max-w-md flex-col bg-white text-slate-900 shadow-xl sm:my-4 sm:min-h-[calc(100vh-2rem)] sm:rounded-[2rem] sm:border sm:border-slate-200">
      <header className="sticky top-0 z-10 border-b border-slate-200 bg-white/90 px-4 pb-3 pt-[max(0.75rem,env(safe-area-inset-top))] backdrop-blur">
        <h1 className="text-lg font-semibold">{title}</h1>
      </header>
      <main className="flex-1 overflow-y-auto px-4 py-4" role="tabpanel" aria-label={title}>
        {tab === "home" ? <Home /> : tab === "search" ? <Search /> : tab === "activity" ? <Activity /> : <Profile />}
      </main>
      <nav className="sticky bottom-0 z-10 grid grid-cols-4 border-t border-slate-200 bg-white pb-[env(safe-area-inset-bottom)]" aria-label="Primary">
        {TABS.map((t) => (
          <button
            key={t.id}
            type="button"
            onClick={() => setTab(t.id)}
            aria-current={tab === t.id ? "page" : undefined}
            className={"flex flex-col items-center gap-0.5 py-2 text-xs " + (tab === t.id ? "text-blue-600" : "text-slate-500 hover:text-slate-900")}
          >
            <span aria-hidden="true" className="text-xl leading-none">
              {t.icon}
            </span>
            {t.label}
          </button>
        ))}
      </nav>
    </div>
  );
}

Dependencies

Documented from the template's package.json. The runtime has a prebuilt dependency set for Vite + React + TypeScript, so installs are fast.

PackageVersion
react^19.1.0
react-dom^19.1.0
vite^6.3.0
@vitejs/plugin-react^4.4.0
tailwindcss^4.1.0
@tailwindcss/vite^4.1.0
typescript^5.8.0

How it runs

Framework
Vite + React + TypeScript
Dev command
npm run dev
Build command
npm run build
Output directory
dist
Port
5173
Opens at
/
Difficulty
beginner

Projects run in an isolated sandbox with a live preview. Read the projects documentation for details on settings, snapshots and deployment.

Need the raw data? GET /api/v1/templates/vite-react-mobile-shell returns this template with its files.

Build on Mobile app shell

Create a free account, start from this template, and ask the AI agent to turn it into your app.