Chapter 4: UI Shell
Let's build something you can see. By the end of this chapter, your app will have a real mobile interface.
This is the chapter where your project stops feeling like a coding exercise and starts looking like an actual app. We'll build the structural skeleton -- the header, bottom navigation bar, and page screens -- that everything else will be built inside.
What You'll Build
- A mobile-first app shell sized to a phone viewport
- A fixed header with the app name
- A bottom navigation bar with tabs for Feed and Profile, plus a center button for adding entries
- Placeholder page components that swap when you tap a tab
- Safe area handling for notched phones (iPhone X and later)
Step 1: Build the Mobile Scaffold
Before writing any code, let's think about what a mobile app shell looks like. Every app you use daily -- Instagram, Twitter, Spotify -- follows the same structural pattern. A header pinned to the top. A navigation bar fixed at the bottom. And a scrollable content area in between. This is the layout users expect, and it is what we are building first.
Let Claude Code handle the implementation:
$ claude "Lets create a simple mobile scafold right now. We are looking for just 3 key thi..."Claude Code will set up several files for you. The header component will be fixed at the top of the screen so it never scrolls away. The bottom navigation will be fixed at the bottom with three interactive elements -- a Feed tab, a center Add button for creating new entries, and a Profile tab. Each tab will have an icon and label, and the active tab will be visually highlighted.
The placeholder pages are intentionally simple for now. Each one shows a centered icon, a title, and a short description of what will eventually be built there. This is a professional practice -- placeholder screens let you build and test navigation before the real content exists.
Step 2: Wire Everything Together
Now we need to connect all these components in the main App file. The key React pattern here is lifting state up -- the parent component owns the state and passes it down to children as props. App.tsx will track which tab is active, and both the content area and the bottom nav will respond to that state.
$ claude "Let's wire things together in the app. Add a useState<Tab> for tracking the acti..."After this step, your app has real navigation. When a user taps a tab in the bottom nav, it calls a function passed down from App.tsx, which updates the state, causing React to re-render and display the corresponding page. This is the same pattern used by virtually every React application with tab-based navigation.
You might wonder why we are not using React Router here. For a simple tab-based app like this, useState is simpler and lighter than adding a router. We are switching between screens in the same view -- there are no URLs to manage, no browser back button behavior to worry about. If the app grew to need nested navigation or deep linking, we would add a router then. Don't over-engineer.
Step 3: Handle the iPhone Notch
Modern iPhones have a notch at the top and a home indicator bar at the bottom. If we don't account for these, our header and navigation will render behind them, making parts of the UI unreachable. This is a two-part fix.
First, update the viewport meta tag:
$ claude "Update index.html to add viewport-fit=cover to the viewport meta tag. This ensur..."The viewport-fit=cover value tells the browser to extend the app behind the notch and home indicator. But extending behind them is only half the solution -- we also need to add padding so our actual content is not obscured.
$ claude "add padding to avoid content being obscured by the notch and home indicator."The safe area insets are CSS environment variables provided by the browser. env(safe-area-inset-top) gives the height of the notch area, and env(safe-area-inset-bottom) gives the height of the home indicator. By adding these as padding, our content stays visible and touchable on every device.
Step 4: Test Your App Shell
Start the dev server:
npm run dev
Open http://localhost:5173 in your browser and check:
- Header appears at the top with the app name
- Bottom nav appears at the bottom with Feed, Add, and Profile
- Feed is active by default (its icon should be highlighted)
- Tap "Add" -- the center content changes to the Create placeholder
- Tap "Profile" -- switches to the Profile placeholder
- Tap "Feed" -- back to the Feed view
- The active tab icon/text changes color when you switch
Test mobile sizing
Open Chrome DevTools (Cmd + Option + I on Mac, Ctrl + Shift + I on Windows), click the device toolbar icon (or press Cmd + Shift + M), and select iPhone 14 from the device dropdown. Your app should look like a real mobile app:
- Content is centered and doesn't stretch to fill a wide screen
- The header and bottom nav span the full width
- Tapping tabs switches the view
- Nothing is hidden behind the notch or home indicator
Step 5: Spruce Up the Design
The scaffold works, but it probably looks a bit plain. Let's give it some personality. We want something that feels polished and premium -- think Headspace or Calm. Clean lines, warm tones, subtle depth.
$ claude "Lets update the design of the header and nav bar to be more polished and premium..."This is a great moment to experiment. Don't like amber? Tell Claude Code to try a different color palette. Want a different vibe? Describe it -- "make it feel more like a journaling app" or "give it a dark mode." One of the best things about working with Claude Code is that design iteration is fast. Try a few different looks and pick the one that feels right to you.
Step 6: Clean Up and Commit
Let's have Claude Code do a final review to make sure everything is clean:
$ claude "Review my project and make sure everything is working correctly. Check for any e..."Once everything looks good, commit your progress:
git add .
git commit -m "Add app shell: header, bottom nav, and tab navigation"
git push
Checkpoint
Your app should now:
- The app has a fixed header at the top of the screen
- A bottom navigation bar with Feed, Add, and Profile
- Clicking each tab switches the displayed page
- The active tab is visually highlighted
- The layout looks correct in Chrome DevTools mobile view
- Safe area padding handles notched iPhones correctly
- The design feels polished and premium with warm amber tones
What's Next
You have an app shell that looks and navigates like a real mobile app. In the next chapter, we'll build the core feature -- creating, viewing, editing, and deleting journal entries. That's where the app comes alive.
Follow @parvsondhi for build threads, tips, and updates on this tutorial.