ReactState ManagementTree TraversalNavigationAccessibility

Build Breadcrumb Navigation

Learn how to build a file-explorer style breadcrumb navigation from scratch. Navigate into nested folders, click breadcrumb segments to jump back, and render folder contents dynamically. A clean interview question that tests tree data structures, state management, and UI patterns.

15 min read7 sections
01

Problem Statement

Build a file explorer with breadcrumb navigation. Users can browse a nested folder structure, clicking folders to navigate deeper and using the breadcrumb trail to jump back to any ancestor folder.

  • Display the current folder's contents (subfolders and files)
  • Clicking a folder navigates into it
  • Show a breadcrumb trail: Home / Documents / Work
  • Clicking any breadcrumb segment navigates back to that folder
  • Differentiate folders (📁) from files (📄) visually
  • Handle edge cases: root level, empty folders, deeply nested paths

Why this question?

Breadcrumb navigation tests your ability to work with tree data structures, manage navigation state as an array (the path), and render dynamic UI based on that state. It's simple enough to implement in 20 minutes but has enough depth for follow-up questions about performance and edge cases.

02

Component Anatomy

Break the UI into its visual parts before writing code:

🔗

Breadcrumb Trail

A horizontal list of clickable segments showing the current path. Each segment (except the last) is a button that navigates back to that folder.

📂

Folder Contents

A list of the current folder's children. Folders are clickable (navigate into), files are not. Shows an empty state if no children.

📄

Folder Item

A single row showing a folder (📁) or file (📄) with its name. Folders have hover states and are buttons; files are static.

/

Separator

A visual divider (/ or >) between breadcrumb segments. Helps users understand the hierarchy at a glance.

Visual structuretext
┌─────────────────────────────────────────────────────┐
Home / Documents / Work                            │  ← Breadcrumb trail
├─────────────────────────────────────────────────────┤
│  📁 Projects                                        │  ← Clickable folder
│  📁 Archive                                         │  ← Clickable folder
│  📄 report.pdf                                      │  ← Non-clickable file
│  📄 notes.txt                                       │  ← Non-clickable file
└─────────────────────────────────────────────────────┘

Clicking "Documents" in the breadcrumbnavigates back to Documents
Clicking "📁 Projects"navigates into Projects folder
03

Data Structure Design

The folder tree is a recursive data structure. Each node can have children that are also nodes. This is the classic tree pattern.

FolderItem interfacetypescript
interface FolderItem {
  id: string;           // Unique identifier
  name: string;         // Display name
  type: "folder" | "file";
  children?: FolderItem[];  // Only folders have children
}
Example file treetypescript
const FILE_TREE: FolderItem = {
  id: "root",
  name: "Home",
  type: "folder",
  children: [
    {
      id: "documents",
      name: "Documents",
      type: "folder",
      children: [
        {
          id: "work",
          name: "Work",
          type: "folder",
          children: [
            { id: "report", name: "report.pdf", type: "file" },
            { id: "slides", name: "slides.pptx", type: "file" },
          ],
        },
        { id: "resume", name: "resume.pdf", type: "file" },
      ],
    },
    {
      id: "photos",
      name: "Photos",
      type: "folder",
      children: [
        { id: "vacation", name: "vacation.jpg", type: "file" },
      ],
    },
  ],
};

Key insight: the path is an array

Instead of storing just the current folder, store the entire path as an array: [Home, Documents, Work]. The last element is the current folder. The array itself is the breadcrumb trail. This makes navigation trivial — push to go deeper, slice to go back.

04

Navigation Logic

Navigation is just two operations on the path array: push (go deeper) and slice (go back). The current folder is always the last element.

1

Initialize the path

Start with the root folder in the path array: [FILE_TREE]. This means we're at the root level, and the breadcrumb shows just 'Home'.

2

Navigate into a folder

When the user clicks a folder, push it onto the path array. The path grows: [Home] → [Home, Documents] → [Home, Documents, Work].

3

Navigate via breadcrumb

When the user clicks a breadcrumb segment at index i, slice the path to keep only elements 0 through i. This 'jumps back' to that folder.

Navigation functionstypescript
const [path, setPath] = useState<FolderItem[]>([FILE_TREE]);

// The current folder is always the last element
const currentFolder = path[path.length - 1];

// Navigate into a subfolder — push it onto the path
const navigateInto = (folder: FolderItem) => {
  setPath((prev) => [...prev, folder]);
};

// Navigate to a breadcrumb segment — slice the path
const navigateTo = (index: number) => {
  setPath((prev) => prev.slice(0, index + 1));
};
ActionPath BeforePath After
Click "Documents" folder[Home][Home, Documents]
Click "Work" folder[Home, Documents][Home, Documents, Work]
Click "Home" breadcrumb[Home, Documents, Work][Home]

Why slice(0, index + 1)?

slice(0, index + 1) keeps elements from index 0 up to and including the clicked index. If the user clicks the breadcrumb at index 1 ("Documents"), we keep [Home, Documents] and discard everything after.

05

Rendering the UI

With the navigation logic in place, rendering is straightforward: map over the path for breadcrumbs, map over currentFolder.children for contents.

Rendering the Breadcrumb Trail

Breadcrumb renderingtypescript
<nav aria-label="Breadcrumb">
  <ol className="flex items-center gap-1 text-sm">
    {path.map((segment, i) => {
      const isLast = i === path.length - 1;
      return (
        <li key={segment.id} className="flex items-center gap-1">
          {/* Separator (skip for first item) */}
          {i > 0 && <span className="text-gray-300">/</span>}

          {/* Last segment is plain text, others are buttons */}
          {isLast ? (
            <span className="text-gray-900 font-medium">
              {segment.name}
            </span>
          ) : (
            <button
              onClick={() => navigateTo(i)}
              className="text-gray-500 hover:text-gray-900"
            >
              {segment.name}
            </button>
          )}
        </li>
      );
    })}
  </ol>
</nav>

Rendering Folder Contents

Folder contents renderingtypescript
<div className="border rounded-lg divide-y">
  {currentFolder.children && currentFolder.children.length > 0 ? (
    currentFolder.children.map((item) => (
      <div key={item.id} className="px-4 py-3 flex items-center gap-3">
        {item.type === "folder" ? (
          <button
            onClick={() => navigateInto(item)}
            className="flex items-center gap-3 w-full text-left"
          >
            <span>📁</span>
            <span>{item.name}</span>
          </button>
        ) : (
          <div className="flex items-center gap-3 text-gray-500">
            <span>📄</span>
            <span>{item.name}</span>
          </div>
        )}
      </div>
    ))
  ) : (
    <div className="px-4 py-10 text-center text-gray-400">
      This folder is empty.
    </div>
  )}
</div>

Don't forget unique keys

Use item.id as the key, not the index. If you accidentally use the parent folder's ID (a common bug), React won't re-render the list correctly when you navigate. This is a classic interview gotcha.

06

Full Implementation

Here's the complete breadcrumb navigation component. Study how the state, navigation functions, and rendering compose together.

breadcrumb-navigation/page.tsxtypescript
"use client";

import { useState } from "react";

interface FolderItem {
  id: string;
  name: string;
  type: "folder" | "file";
  children?: FolderItem[];
}

const FILE_TREE: FolderItem = {
  id: "root",
  name: "Home",
  type: "folder",
  children: [
    {
      id: "documents",
      name: "Documents",
      type: "folder",
      children: [
        {
          id: "work",
          name: "Work",
          type: "folder",
          children: [
            { id: "report", name: "report.pdf", type: "file" },
            { id: "slides", name: "slides.pptx", type: "file" },
          ],
        },
        {
          id: "personal",
          name: "Personal",
          type: "folder",
          children: [
            { id: "resume", name: "resume.pdf", type: "file" },
          ],
        },
      ],
    },
    {
      id: "photos",
      name: "Photos",
      type: "folder",
      children: [
        { id: "vacation", name: "vacation.jpg", type: "file" },
      ],
    },
  ],
};

export default function BreadcrumbNavigationPage() {
  const [path, setPath] = useState<FolderItem[]>([FILE_TREE]);
  const currentFolder = path[path.length - 1];

  const navigateInto = (folder: FolderItem) => {
    setPath((prev) => [...prev, folder]);
  };

  const navigateTo = (index: number) => {
    setPath((prev) => prev.slice(0, index + 1));
  };

  return (
    <div className="max-w-2xl mx-auto p-6">
      {/* Breadcrumb trail */}
      <nav aria-label="Breadcrumb" className="mb-6">
        <ol className="flex items-center gap-1 text-sm">
          {path.map((segment, i) => {
            const isLast = i === path.length - 1;
            return (
              <li key={segment.id} className="flex items-center gap-1">
                {i > 0 && <span className="text-gray-300">/</span>}
                {isLast ? (
                  <span className="text-gray-900 font-medium">
                    {segment.name}
                  </span>
                ) : (
                  <button
                    onClick={() => navigateTo(i)}
                    className="text-gray-500 hover:text-gray-900"
                  >
                    {segment.name}
                  </button>
                )}
              </li>
            );
          })}
        </ol>
      </nav>

      {/* Folder contents */}
      <div className="border rounded-lg divide-y">
        {currentFolder.children?.length ? (
          currentFolder.children.map((item) => (
            <div key={item.id} className="px-4 py-3">
              {item.type === "folder" ? (
                <button
                  onClick={() => navigateInto(item)}
                  className="flex items-center gap-3 w-full"
                >
                  <span>📁</span>
                  <span>{item.name}</span>
                </button>
              ) : (
                <div className="flex items-center gap-3 text-gray-500">
                  <span>📄</span>
                  <span>{item.name}</span>
                </div>
              )}
            </div>
          ))
        ) : (
          <div className="px-4 py-10 text-center text-gray-400">
            This folder is empty.
          </div>
        )}
      </div>
    </div>
  );
}
✓ Done

Path as single source of truth

The path array is the only state. The current folder and breadcrumb trail are both derived from it. No redundant state to keep in sync.

✓ Done

Functional state updates

Using setPath(prev => ...) ensures updates are based on the latest state, avoiding stale closure bugs in event handlers.

→ Could add

Lazy loading for large trees

For file systems with thousands of items, fetch children on-demand when a folder is opened instead of loading the entire tree upfront.

→ Could add

Keyboard navigation

Add arrow key support to move focus between items, Enter to navigate into folders, and Backspace to go up one level.

07

Common Interview Follow-up Questions

After building the breadcrumb navigation, interviewers dig into edge cases and enhancements:

Q:How would you handle a very deep folder structure (20+ levels)?

A: Truncate the breadcrumb trail with an ellipsis. Show the first segment (Home), an ellipsis dropdown with hidden segments, and the last 2-3 segments. Clicking the ellipsis reveals a dropdown menu with all hidden segments.

Q:How would you add URL-based navigation (deep linking)?

A: Store the path in the URL as a query param or path segments (e.g., /files/documents/work). On page load, parse the URL and traverse the tree to rebuild the path array. Use router.push() to update the URL when navigating.

Q:What if the folder tree is fetched from an API?

A: Start with just the root folder. When the user clicks a folder, fetch its children from the API and cache them. Show a loading spinner while fetching. This is lazy loading — you only fetch what the user actually visits.

Q:How would you handle folders with hundreds of items?

A: Add pagination or virtual scrolling. Fetch items in batches (e.g., 50 at a time) with a 'Load more' button or infinite scroll. Use react-window for virtual scrolling if rendering thousands of items.

Q:How would you implement search/filter within a folder?

A: Add a search input above the folder contents. Filter currentFolder.children by name using a case-insensitive includes() check. For deep search across all folders, you'd need to traverse the entire tree or use a server-side search API.

Q:What about accessibility for screen readers?

A: Use semantic HTML: nav with aria-label='Breadcrumb', ol/li for the trail. Add aria-current='page' to the last breadcrumb segment. Use role='list' for folder contents. Ensure all interactive elements are focusable and have accessible names.

Q:How would you add drag-and-drop to move files between folders?

A: Use the HTML5 Drag and Drop API or a library like dnd-kit. Track the dragged item and the drop target folder. On drop, update the tree structure: remove the item from its current parent's children and add it to the target folder's children.

Ready to build it yourself?

We've set up the folder tree and UI shell. Implement the navigation logic and rendering from scratch.

Built for developers, by developers. Happy coding! 🚀