Build a Phone Screen–style web app (LLD)
Lock screen → home with app grid → app screens; each app a self-contained route. State: current app (route), back-stack, modal layer, notifications. App registry pattern: each app declares its icon, name, route, and entry component. Status bar + dock fixed; gesture/keyboard nav between screens. Lazy-load each app's bundle.
A phone-screen-style web app simulates iOS / Android — lock screen, home with apps, individual apps with their own state. It's a routing + composition exercise.
1. Top-level structure
<Phone>
├ <StatusBar /> (time, battery, signal — fixed top)
├ <ScreenStack>
│ ├ <LockScreen /> (initial)
│ ├ <HomeScreen apps={apps} onOpen={openApp} />
│ └ <AppScreen current={openAppId} />
├ <Dock /> (fixed bottom)
└ <NotificationLayer />2. App registry
Each app is a record:
const apps = [
{ id: "messages", name: "Messages", icon: MessageIcon, Component: lazy(() => import("./apps/Messages")) },
{ id: "photos", name: "Photos", icon: PhotoIcon, Component: lazy(() => import("./apps/Photos")) },
// ...
];- Registering an app = adding one entry.
- Apps are lazy-loaded so the initial bundle is small.
3. Navigation state
const [route, setRoute] = useState({ stack: ["home"] });
const openApp = (id) => setRoute({ stack: [...route.stack, id] });
const goBack = () => setRoute({ stack: route.stack.slice(0, -1) });The current route is the top of the stack. Open app pushes; back pops.
4. Lock screen
{!unlocked ? (
<LockScreen onUnlock={() => setUnlocked(true)} />
) : (
<HomeScreen ... />
)}PIN / pattern / biometric simulation; transitions to home on success.
5. Home grid
<div className="grid">
{apps.map((app) => (
<button key={app.id} onClick={() => openApp(app.id)}>
<app.icon />
<span>{app.name}</span>
</button>
))}
</div>CSS grid; tap target ~60px+; accessibility labels.
6. App screen rendering
<AppScreen>
{openAppId ? <Suspense fallback={<Splash />}>{<Component />}</Suspense> : <Home />}
</AppScreen>Each app has its own <Component> and manages its internal routes/state.
7. Transitions
- Open: app icon scales up, screen fades/slides in.
- Close: reverse, with back-gesture or button.
- Use Framer Motion or CSS transitions; respect
prefers-reduced-motion.
8. Gestures + keyboard
- Swipe up → back to home.
- Swipe down from top → notifications panel.
- ESC / browser back → back in stack.
- Tab through apps in the grid.
Web doesn't have native gestures; intercept pointermove + threshold for swipe simulation.
9. Notifications
- A separate layer above the screen stack.
- Push-style notifications (toast / banner) animated in.
- Each app can register a notification handler.
- Tapping a notification opens the relevant app.
10. Status bar / dock
Fixed positioning; safe areas for notch (env(safe-area-inset-top) on iOS Safari).
11. Persistence
- Selected app, lock state, app-specific state in localStorage or sessionStorage.
- On reload, restore where the user was (with optional unlock requirement).
12. Bundle strategy
- Initial bundle: framework + home + lock screen only.
- Each app: separately lazy-loaded chunk.
- Use
<link rel="prefetch">for the dock apps so they open instantly.
13. Accessibility
- Each app icon is a
<button>with label. - Status bar info read by screen readers via
aria-label(don't speak time every second; only on focus). - Focus management when entering/exiting apps.
- Reduced-motion: disable animations.
14. Real-world parallels
- iOS / Android / Windows Phone home screens.
- Web OSes (CrossOver, ChromeOS launcher).
- Internal portal apps that group multiple tools (Salesforce App Cloud).
Interview framing
"Composition: status bar + screen stack + dock + notification layer. The screen stack is a tiny back-stack — open pushes, back pops. Apps live in a registry — each declares id, name, icon, and a lazy-loaded Component — so adding an app is one entry. Lock screen gates entry; home shows the grid; selecting an app renders its Component inside the AppScreen with Suspense. Gestures simulate swipe-up-to-home and swipe-down-for-notifications via pointer events with thresholds. Bundle: framework + home initially; each app lazy-loaded; prefetch the dock apps. Persistence in localStorage. Accessibility: focus management, labels, reduced-motion."
Follow-up questions
- •Why use a registry pattern for apps?
- •How do you simulate swipe gestures on web?
- •How do you handle the back button vs in-app navigation?
- •How do you keep the initial bundle small with many apps?
Common mistakes
- •Loading all apps upfront — huge bundle.
- •No back-stack — confused navigation.
- •Status bar info that animates every second (battery, time).
- •Modal layer that blocks the status bar.
Performance considerations
- •Lazy-load apps. Memoize home grid. Use transform-only for transitions. Prefetch likely-next app.
Edge cases
- •Refresh mid-app — restore?
- •Notification while in an app — toast vs banner vs interrupt.
- •Multi-window simulation.
Real-world examples
- •iOS/Android home screens; ChromeOS launcher; internal multi-tool portals.