🔴 LIVE 1-ON-1 MASTERCLASS

Module 1 • Lesson 04 Bottom Tabs & Multi-Layout Architecture

Mastering Route Groups (tabs), Tabs Navigator, @expo/vector-icons, Dynamic Badges, and Nested Stack + Tab Hierarchies.

👨‍🏫 Live Teacher Concept Breakdown (Simple Bhasha Me)

Altamash, Bottom Tab navigation ko master karne ke liye ye 3 golden rules yaad rakho:

1. 📁 Route Groups (tabs) (Invisible URL Folder):

Jab folder ka naam parentheses me hota hai jaise app/(tabs)/, toh URL me /(tabs) nahi dikhta! app/(tabs)/index.tsx seedha / route banta hai aur analytics.tsx seedha /analytics banta hai. Parentheses group banane ke liye hote hain, URL ko clean rakhne ke liye.

2. 📱 Tabs Navigator vs Stack Navigator:

Stack me screens ek ke upar ek push/pop hoti hain (forward/backward history). Tabs me screens parallel hoti hain (Instagram ya Spotify ke bottom bar jaisi) — user instant ek tap se switch karta hai aur screen state memory me persist rehti hai!

3. 👑 Root Stack + Nested Tabs (Production Standard Pattern):

Root app/_layout.tsx pure app ko ek Stack banata hai. Uske andar (tabs) ko as a screen load karte hain. Jab user /add-meal modal kholta hai, toh wo modal poore bottom tab bar ke upar smooth slide hota hai!

🎯 Today's Target Milestone

1. Organize routes cleanly using Expo Router Route Groups (tabs).
2. Build a modern floating dark-themed Tabs Navigator in app/(tabs)/_layout.tsx.
3. Integrate @expo/vector-icons (Ionicons) with smooth focused/unfocused state toggling.
4. Add Dynamic Tab Badges for real-time notification/item counters.
5. Complete Module 1 by upgrading MacroZone into a 3-Tab powerhouse: Today, Analytics, and Goals.

1. Route Groups (tabs) & Multi-Layout Architecture

In Expo Router v6, wrapping a directory name in parentheses like (tabs) creates a Route Group. Route groups allow you to organize screens and assign dedicated layouts without adding extra segments to the web URL or deep-link path.

🏗️ Production Multi-Layout Routing Tree
Root

app/_layout.tsx → Root Stack Navigator

Global theme provider, Root Stack with modal overlay sheets (/add-meal) and detail pages (/meal/[id]).

↓ embeds (headerShown: false)
Group

app/(tabs)/_layout.tsx → Bottom Tabs Navigator

Renders the bottom navigation bar, icon toggles, active colors, and tab badges.

↓ manages parallel tabs
(tabs)/index.tsx
Path: / (Today Tab)
(tabs)/analytics.tsx
Path: /analytics (Charts Tab)
(tabs)/goals.tsx
Path: /goals (Targets Tab)
Isolated Example: app/_layout.tsx (Root Stack Nesting Tabs)
import { Stack } from "expo-router"; // 👈 Stack navigator import

export default function RootLayout() {
  return (
    // 👈 Root Stack manages global screen transitions & overlays
    <Stack
      screenOptions={{
        headerStyle: { backgroundColor: "#020617" }, // 👈 Dark slate-950 header background
        headerTintColor: "#38bdf8",                  // 👈 Sky-400 accent for titles & back buttons
        contentStyle: { backgroundColor: "#020617" }, // 👈 OLED black canvas background
      }}
    >
      {/* 👈 1. The entire (tabs) group is treated as one screen without a native stack header */}
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />

      {/* 👈 2. Modal Screen: slides from bottom over the entire tab bar */}
      <Stack.Screen name="add-meal" options={{ title: "Log Meal", presentation: "modal" }} />

      {/* 👈 3. Dynamic Detail Screen: card push transition */}
      <Stack.Screen name="meal/[id]" options={{ title: "Meal Details" }} />
    </Stack>
  );
}

🔍 Line-by-Line Explanation:

Code Line Kya Karta Hai? (Simple Explanation)
<Stack.Screen name="(tabs)" options={{ headerShown: false }} /> Root stack ko batata hai ki (tabs) folder ek screen hai, aur uska default Stack header hide kar do taaki bottom tabs apna khud ka header control karein.
presentation: "modal" Jab /add-meal open hoga, toh wo screen bottom se slide-up sheet ke roop me tab bar ke upar render hogi.
contentStyle: { backgroundColor: "#020617" } Screen transitions ke dauran white flash na aaye isliye default dark canvas color set karta hai.

2. Building the Floating Dark Tab Bar with @expo/vector-icons

To build a high-polish bottom bar in app/(tabs)/_layout.tsx, we use the Tabs primitive from expo-router and icon components from @expo/vector-icons (e.g. Ionicons).

🎨 Tab Bar Anatomy & Icon State Switching
✨ Active Tab (focused = true) Active State
📊
Analytics (Filled Icon)

Renders filled icon stats-chart with vibrant emerald tint (#10b981).

💤 Inactive Tab (focused = false) Inactive State
📈
Analytics (Outline Icon)

Renders outline icon stats-chart-outline with muted slate tint (#64748b).

Isolated Example: app/(tabs)/_layout.tsx (Floating Dark Tab Bar)
import { Tabs } from "expo-router";                 // 👈 Bottom Tabs Navigator
import { Ionicons } from "@expo/vector-icons";      // 👈 High-res vector icons

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: "#10b981",          // 👈 Emerald-500 active tab highlight
        tabBarInactiveTintColor: "#64748b",        // 👈 Slate-500 muted inactive color
        tabBarStyle: {
          backgroundColor: "#0f172a",              // 👈 Dark Slate-900 surface
          borderTopColor: "#1e293b",               // 👈 Subtle Slate-800 divider border
          height: 62,                              // 👈 Comfortable touch height
          paddingBottom: 8,                         // 👈 Safe inset spacing
          paddingTop: 6,
        },
        headerStyle: { backgroundColor: "#020617" },// 👈 Dark top header
        headerTintColor: "#f8fafc",                // 👈 White header text
      }}
    >
      {/* 👈 Tab 1: Today Screen */}
      <Tabs.Screen
        name="index"
        options={{
          title: "Today",
          tabBarIcon: ({ color, focused }) => (
            <Ionicons
              name={focused ? "calendar" : "calendar-outline"}
              size={22}
              color={color}
            />
          ),
          tabBarBadge: 3,                          // 👈 Dynamic notification badge count!
          tabBarBadgeStyle: {
            backgroundColor: "#10b981",
            color: "#020617",
            fontWeight: "bold",
          },
        }}
      />

      {/* 👈 Tab 2: Analytics Screen */}
      <Tabs.Screen
        name="analytics"
        options={{
          title: "Analytics",
          tabBarIcon: ({ color, focused }) => (
            <Ionicons
              name={focused ? "stats-chart" : "stats-chart-outline"}
              size={22}
              color={color}
            />
          ),
        }}
      />

      {/* 👈 Tab 3: Goals Screen */}
      <Tabs.Screen
        name="goals"
        options={{
          title: "Goals",
          tabBarIcon: ({ color, focused }) => (
            <Ionicons
              name={focused ? "flag" : "flag-outline"}
              size={22}
              color={color}
            />
          ),
        }}
      />
    </Tabs>
  );
}

🔍 Line-by-Line Explanation:

Code Line Kya Karta Hai? (Simple Explanation)
import { Tabs } from "expo-router" Native bottom tab navigation container component import karta hai.
tabBarActiveTintColor: "#10b981" Jo tab currently open/active hai uske icon aur text ko vibrant emerald green color deta hai.
tabBarStyle: { backgroundColor: "#0f172a" } Bottom bar ko modern dark slate background aur top border styling deta hai.
tabBarIcon: ({ color, focused }) => (...) Tab ke active/inactive hone par filled (calendar) ya outline (calendar-outline) icon conditionally render karta hai.
tabBarBadge: 3 Icon ke top-right corner par unread/logged items ka badge counter bubble render karta hai.

3. 🎨 Tailwind CSS (NativeWind) Utility Decoder for Tab UI

Here is the visual cheat sheet for all Tailwind utility classes used in our new 3-tab screens:

📊 Progress Bars & Metric Distribution Classes

🌑 Card Surface & Spacing Tokens

4. Complete Integration: MacroZone 3-Tab Architecture

Let's inspect how all screens in MacroZone are now organized cleanly across our 3 tabs:

app/(tabs)/index.tsx (Tab 1: Today Dashboard & Quick Logger)
import { useState } from "react";
import { View, Text, TextInput, Pressable, FlatList } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useRouter, Link } from "expo-router";                 // 👈 Router hooks
import { Ionicons } from "@expo/vector-icons";                // 👈 Icons

export interface MealItem {
  id: string;
  name: string;
  calories: number;
  protein: number;
  time: string;
}

export default function TodayScreen() {
  const router = useRouter(); // 👈 Navigation controller

  const [meals, setMeals] = useState<MealItem[]>([
    { id: "1", name: "Oats & Whey Protein", calories: 420, protein: 35, time: "8:30 AM" },
    { id: "2", name: "Grilled Chicken Salad", calories: 510, protein: 48, time: "1:15 PM" },
    { id: "3", name: "Boiled Eggs & Almonds", calories: 280, protein: 18, time: "5:00 PM" },
  ]);

  const [name, setName] = useState("");
  const [calories, setCalories] = useState("");
  const [protein, setProtein] = useState("");

  const totalCalories = meals.reduce((sum, m) => sum + m.calories, 0);
  const totalProtein = meals.reduce((sum, m) => sum + m.protein, 0);
  const calorieTarget = 2400;

  const handleAddMeal = () => {
    if (!name.trim() || !calories.trim()) return;
    const newMeal: MealItem = {
      id: Date.now().toString(),
      name: name.trim(),
      calories: parseInt(calories) || 0,
      protein: parseInt(protein) || 0,
      time: "Just now",
    };
    setMeals([newMeal, ...meals]);
    setName("");
    setCalories("");
    setProtein("");
  };

  const handleDeleteMeal = (id: string) => {
    setMeals(meals.filter((m) => m.id !== id));
  };

  return (
    <SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
      {/* ── Top Header with Modal Action ── */}
      <View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4 shadow-2xl">
        <View className="flex-row items-center justify-between mb-3">
          <View>
            <Text className="text-xs font-bold uppercase tracking-wider text-emerald-400">
              Daily Fuel
            </Text>
            <Text className="text-2xl font-bold text-white">Today's Meals</Text>
          </View>

          {/* 👈 Opens Modal Sheet on Top of Tabs */}
          <Link href="/add-meal" asChild>
            <Pressable className="bg-emerald-600 active:bg-emerald-700 px-3.5 py-2 rounded-2xl flex-row items-center gap-1.5">
              <Ionicons name="add-circle" size={16} color="#fff" />
              <Text className="text-white text-xs font-bold">Modal</Text>
            </Pressable>
          </Link>
        </View>

        <View className="flex-row gap-3">
          <View className="flex-1 bg-slate-950/80 border border-slate-800 p-3 rounded-2xl">
            <Text className="text-slate-400 text-xs">Calories Eaten</Text>
            <Text className="text-white text-xl font-bold mt-1">{totalCalories} <Text className="text-xs text-slate-500 font-normal">/ {calorieTarget}</Text></Text>
          </View>
          <View className="flex-1 bg-slate-950/80 border border-slate-800 p-3 rounded-2xl">
            <Text className="text-slate-400 text-xs">Total Protein</Text>
            <Text className="text-emerald-400 text-xl font-bold mt-1">{totalProtein}g</Text>
          </View>
        </View>
      </View>

      {/* ── Quick Add Form ── */}
      <View className="bg-slate-900/90 border border-slate-800 p-4 rounded-2xl mb-4 gap-2.5">
        <TextInput
          value={name}
          onChangeText={setName}
          placeholder="Food Name (e.g. Greek Yogurt)"
          placeholderTextColor="#64748b"
          className="bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
        />
        <View className="flex-row gap-2.5">
          <TextInput
            value={calories}
            onChangeText={setCalories}
            placeholder="Calories (kcal)"
            placeholderTextColor="#64748b"
            keyboardType="numeric"
            className="flex-1 bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
          />
          <TextInput
            value={protein}
            onChangeText={setProtein}
            placeholder="Protein (g)"
            placeholderTextColor="#64748b"
            keyboardType="numeric"
            className="flex-1 bg-slate-950 border border-slate-800 text-white px-3.5 py-2.5 rounded-xl text-sm"
          />
        </View>
        <Pressable
          onPress={handleAddMeal}
          className="bg-emerald-600 active:bg-emerald-700 py-3 rounded-xl items-center mt-1"
        >
          <Text className="text-white font-semibold text-sm">+ Quick Log Meal</Text>
        </Pressable>
      </View>

      {/* ── Meal List ── */}
      <FlatList
        data={meals}
        keyExtractor={(item) => item.id}
        showsVerticalScrollIndicator={false}
        renderItem={({ item }) => (
          <Pressable
            onPress={() =>
              router.push({
                pathname: "/meal/[id]",
                params: {
                  id: item.id,
                  name: item.name,
                  calories: item.calories.toString(),
                  protein: item.protein.toString(),
                },
              })
            }
            className="bg-slate-900 border border-slate-800/80 p-3.5 rounded-2xl flex-row justify-between items-center mb-2.5 active:bg-slate-800"
          >
            <View className="flex-1 mr-2">
              <Text className="text-white font-medium text-sm">{item.name}</Text>
              <Text className="text-slate-400 text-xs mt-0.5">{item.time} • {item.protein}g protein</Text>
            </View>
            <View className="flex-row items-center gap-2">
              <View className="bg-emerald-500/10 border border-emerald-500/20 px-2.5 py-1 rounded-xl">
                <Text className="text-emerald-300 font-bold text-xs">{item.calories} kcal</Text>
              </View>
              <Pressable
                onPress={(e) => {
                  e.stopPropagation?.();
                  handleDeleteMeal(item.id);
                }}
                className="w-7 h-7 rounded-lg bg-red-500/10 border border-red-500/20 items-center justify-center active:bg-red-500/20"
              >
                <Text className="text-red-400 text-xs font-bold">✕</Text>
              </Pressable>
            </View>
          </Pressable>
        )}
      />
    </SafeAreaView>
  );
}
app/(tabs)/analytics.tsx (Tab 2: Visual Macro Breakdown & Daily Stats)
import { View, Text, ScrollView } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Ionicons } from "@expo/vector-icons";

export default function AnalyticsScreen() {
  const caloriesConsumed = 1210;
  const calorieGoal = 2400;
  const proteinCurrent = 101;
  const proteinGoal = 160;
  const carbsCurrent = 115;
  const carbsGoal = 220;
  const fatsCurrent = 38;
  const fatsGoal = 70;

  return (
    <SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
      <ScrollView showsVerticalScrollIndicator={false}>
        
        {/* Header Title */}
        <View className="mb-4">
          <Text className="text-xs font-bold uppercase tracking-wider text-sky-400">
            Performance & Nutrition
          </Text>
          <Text className="text-2xl font-bold text-white">Daily Analytics</Text>
        </View>

        {/* Calorie Goal Ring / Bar Card */}
        <View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-4 shadow-2xl">
          <View className="flex-row justify-between items-center mb-2">
            <Text className="text-slate-300 font-semibold">Energy Balance</Text>
            <Text className="text-sky-400 font-bold text-sm">
              {Math.round((caloriesConsumed / calorieGoal) * 100)}% of goal
            </Text>
          </View>
          <View className="h-3 w-full bg-slate-950 rounded-full overflow-hidden mb-3 border border-slate-800">
            <View
              className="h-full bg-sky-500 rounded-full"
              style={{ width: `${Math.min(100, (caloriesConsumed / calorieGoal) * 100)}%` }}
            />
          </View>
          <View className="flex-row justify-between text-xs">
            <Text className="text-slate-400 text-xs">{caloriesConsumed} kcal eaten</Text>
            <Text className="text-slate-400 text-xs">{calorieGoal - caloriesConsumed} kcal remaining</Text>
          </View>
        </View>

        {/* Macro Nutrient Breakdown Cards */}
        <Text className="text-sm font-bold text-slate-300 uppercase tracking-wider mb-3">
          Macronutrient Split
        </Text>

        {/* Protein Progress */}
        <View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-3 border-l-4 border-l-emerald-500">
          <View className="flex-row justify-between items-center mb-1.5">
            <Text className="text-white font-bold text-sm">🍗 Protein</Text>
            <Text className="text-emerald-400 font-bold text-sm">{proteinCurrent}g / {proteinGoal}g</Text>
          </View>
          <View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
            <View
              className="h-full bg-emerald-500 rounded-full"
              style={{ width: `${(proteinCurrent / proteinGoal) * 100}%` }}
            />
          </View>
        </View>

        {/* Carbs Progress */}
        <View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-3 border-l-4 border-l-amber-500">
          <View className="flex-row justify-between items-center mb-1.5">
            <Text className="text-white font-bold text-sm">🌾 Carbohydrates</Text>
            <Text className="text-amber-400 font-bold text-sm">{carbsCurrent}g / {carbsGoal}g</Text>
          </View>
          <View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
            <View
              className="h-full bg-amber-500 rounded-full"
              style={{ width: `${(carbsCurrent / carbsGoal) * 100}%` }}
            />
          </View>
        </View>

        {/* Fats Progress */}
        <View className="bg-slate-900 border border-slate-800 rounded-2xl p-4 mb-4 border-l-4 border-l-rose-500">
          <View className="flex-row justify-between items-center mb-1.5">
            <Text className="text-white font-bold text-sm">🥑 Healthy Fats</Text>
            <Text className="text-rose-400 font-bold text-sm">{fatsCurrent}g / {fatsGoal}g</Text>
          </View>
          <View className="h-2 w-full bg-slate-950 rounded-full overflow-hidden">
            <View
              className="h-full bg-rose-500 rounded-full"
              style={{ width: `${(fatsCurrent / fatsGoal) * 100}%` }}
            />
          </View>
        </View>

        {/* Streak Banner */}
        <View className="bg-slate-900 border border-slate-800 p-4 rounded-2xl flex-row items-center gap-3 mb-6">
          <View className="w-10 h-10 rounded-xl bg-amber-500/10 border border-amber-500/20 items-center justify-center">
            <Ionicons name="flame" size={22} color="#f59e0b" />
          </View>
          <View>
            <Text className="text-white font-bold text-sm">5-Day Consistency Streak! 🔥</Text>
            <Text className="text-slate-400 text-xs">You've hit your protein goal 5 days in a row.</Text>
          </View>
        </View>

      </ScrollView>
    </SafeAreaView>
  );
}
app/(tabs)/goals.tsx (Tab 3: Nutritional Target Settings)
import { useState } from "react";
import { View, Text, TextInput, Pressable, ScrollView, Alert } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Ionicons } from "@expo/vector-icons";

export default function GoalsScreen() {
  const [calorieTarget, setCalorieTarget] = useState("2400");
  const [proteinTarget, setProteinTarget] = useState("160");
  const [carbsTarget, setCarbsTarget] = useState("220");
  const [fatsTarget, setFatsTarget] = useState("70");
  const [activePlan, setActivePlan] = useState<"cut" | "maintain" | "bulk">("maintain");

  const handleSave = () => {
    Alert.alert("Goals Updated! 🎯", "Your daily macro targets have been successfully saved.");
  };

  const handlePreset = (plan: "cut" | "maintain" | "bulk") => {
    setActivePlan(plan);
    if (plan === "cut") {
      setCalorieTarget("2000");
      setProteinTarget("180");
      setCarbsTarget("160");
      setFatsTarget("55");
    } else if (plan === "maintain") {
      setCalorieTarget("2400");
      setProteinTarget("160");
      setCarbsTarget("220");
      setFatsTarget("70");
    } else if (plan === "bulk") {
      setCalorieTarget("2850");
      setProteinTarget("170");
      setCarbsTarget("320");
      setFatsTarget("85");
    }
  };

  return (
    <SafeAreaView className="flex-1 bg-slate-950 px-5 pt-2">
      <ScrollView showsVerticalScrollIndicator={false}>
        {/* Header */}
        <View className="mb-4">
          <Text className="text-xs font-bold uppercase tracking-wider text-purple-400">
            Preferences & Targets
          </Text>
          <Text className="text-2xl font-bold text-white">Nutritional Goals</Text>
        </View>

        {/* Preset Selector */}
        <View className="flex-row gap-2 mb-5">
          {(["cut", "maintain", "bulk"] as const).map((plan) => (
            <Pressable
              key={plan}
              onPress={() => handlePreset(plan)}
              className={`flex-1 py-2.5 rounded-xl items-center border ${
                activePlan === plan
                  ? "bg-purple-600 border-purple-500"
                  : "bg-slate-900 border-slate-800"
              }`}
            >
              <Text
                className={`text-xs font-bold uppercase tracking-wider ${
                  activePlan === plan ? "text-white" : "text-slate-400"
                }`}
              >
                {plan === "cut" ? "🔥 Cut" : plan === "maintain" ? "⚖️ Maintain" : "💪 Bulk"}
              </Text>
            </Pressable>
          ))}
        </View>

        {/* Input Target Cards */}
        <View className="bg-slate-900 border border-slate-800 rounded-3xl p-5 mb-5 gap-4">
          <View>
            <Text className="text-slate-400 text-xs font-bold uppercase mb-1">Daily Calorie Target (kcal)</Text>
            <TextInput
              value={calorieTarget}
              onChangeText={setCalorieTarget}
              keyboardType="numeric"
              className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base font-bold"
            />
          </View>

          <View>
            <Text className="text-slate-400 text-xs font-bold uppercase mb-1">Protein Target (grams)</Text>
            <TextInput
              value={proteinTarget}
              onChangeText={setProteinTarget}
              keyboardType="numeric"
              className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base font-bold"
            />
          </View>

          <View className="flex-row gap-3">
            <View className="flex-1">
              <Text className="text-slate-400 text-xs font-bold uppercase mb-1">Carbs (g)</Text>
              <TextInput
                value={carbsTarget}
                onChangeText={setCarbsTarget}
                keyboardType="numeric"
                className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base font-bold"
              />
            </View>
            <View className="flex-1">
              <Text className="text-slate-400 text-xs font-bold uppercase mb-1">Fats (g)</Text>
              <TextInput
                value={fatsTarget}
                onChangeText={setFatsTarget}
                keyboardType="numeric"
                className="bg-slate-950 border border-slate-800 text-white px-4 py-3 rounded-xl text-base font-bold"
              />
            </View>
          </View>
        </View>

        <Pressable
          onPress={handleSave}
          className="bg-purple-600 active:bg-purple-700 py-3.5 rounded-2xl items-center flex-row justify-center gap-2 mb-8"
        >
          <Ionicons name="checkmark-done-circle" size={18} color="#fff" />
          <Text className="text-white font-bold text-sm">Save Nutritional Goals</Text>
        </Pressable>
      </ScrollView>
    </SafeAreaView>
  );
}

5. Interactive Live Knowledge Checks

Test your mental model on route groups, tab bar options, and vector icon switching with these 4 interactive challenges:

Check 1: What is the primary purpose of naming a folder with parentheses like (tabs)?

Check 2: How do you display a dynamic notification counter bubble over a tab icon?

Check 3: What parameter does tabBarIcon receive to toggle between filled and outline icons?

Check 4: How does a Root Stack with modal presentation interact with nested Bottom Tabs?

🛠️ Live Teacher Coding Challenge

Let's verify our completed Module 1 architecture inside your live mobile app:

  1. Examine app/(tabs)/_layout.tsx — observe how the floating dark tab bar, Ionicons, and active tints are configured.
  2. Switch between the Today, Analytics, and Goals tabs on your phone with Expo Go.
  3. Tap the + Modal button on the Today tab — verify that the slide-up modal smoothly overlays the entire bottom bar!
  4. Tap on any meal card — verify that dynamic navigation to /meal/[id] continues working seamlessly.

6. Primary Sources & Reference Material

← Lesson 03: Expo Router Navigation Lesson 05: Zustand Stores →

💬 Ask Your Teacher: What questions do you have about route groups (tabs), custom tab bars, or vector icon styling? Let's discuss in the chat!