IT Asset Manager Dashboard with React

Build a multi-page admin dashboard with React components, props, and routing.

Introduction

30 Second Summary

Every IT team tracks laptops, servers, software licenses, and support tickets. Without a central dashboard, that information lives in scattered spreadsheets and email threads.

In this project, you will build an IT Asset Management dashboard using React. You will create stat cards, a bar chart, and a multi-page layout with sidebar navigation.

What You'll Build

You will open your browser and see a polished admin panel with a dark sidebar, summary stat cards, and a bar chart of monthly asset acquisitions, all navigable across five pages without a single page reload.

By the end of this project, you'll have:

  • A Dashboard page with 4 stat cards and a Recharts bar chart showing monthly asset acquisitions from mock data.
  • A polished app shell with a dark sidebar and top navbar, powered by React Router for instant page switches with no reloads.
  • Five navigable pages with active link highlighting, ready for a Django backend connection in the next project.
  • Secret Mission: Add a mobile-responsive sidebar toggle that hides the navigation on small screens and opens it with a hamburger menu button.

Are there any prerequisites?

This project assumes you are comfortable with JavaScript basics like variables, functions, and arrays. You should also have Node.js and a code editor like VS Code installed on your machine.

Before We Start

Before we dive in, let's get clear on what you're about to build and why it matters.

You're creating an IT Asset Management dashboard with React, complete with stat cards, a bar chart, and multi-page navigation that will later connect to a Django backend.

Set Up the React Project

Before you can build any UI, you need a working development environment. Vite scaffolds a ready-to-run React project in seconds, giving you a dev server with instant hot-reload so you see every change the moment you save.

In this step, you will scaffold a new React project with Vite and install the two libraries this project depends on: React Router for page navigation and Recharts for the dashboard chart.

In this step, get ready to:
  • Scaffold a React project with Vite and install React Router and Recharts.
  • Clean up the scaffold boilerplate and add the project's CSS styles.
  • Start the dev server and confirm the app renders in your browser.
Scaffold the project with Vite and install dependencies

Vite is a modern build tool that replaces the deprecated Create React App. It generates a project folder with everything you need to start writing React code.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the React project by running this command:
npm create vite@latest asset-manager -- --template react

What does this command do?

npm create vite@latest downloads and runs the latest Vite project scaffolder.

asset-manager sets the folder name. --template react tells Vite to use the React JavaScript template, which skips the interactive template-selection prompts.

Seeing a prompt about installing create-vite?

If this is your first time using Vite, npm will ask "Need to install the following packages: create-vite. Ok to proceed? (y)". Type y and press Enter to continue.

  • Confirm the project folder was created by running:
ls

You should see asset-manager listed in the output.

  • Move into the project folder and install the base dependencies by running these commands:
cd asset-manager
npm install

You will see a progress bar followed by a summary like "added 189 packages". The exact number may vary.

  • Install React Router for navigation and Recharts for charting by running:
npm install react-router recharts

Seeing npm errors?

  • Make sure you are inside the asset-manager folder. Run pwd to check your current directory.
  • If you see a permissions error, try prefixing the command with sudo on macOS/Linux.
  • Help me troubleshoot npm install errors in my Vite React project.
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the asset-manager folder, then click Open.

What is Vite?

Vite is the recommended build tool for new React projects, replacing the deprecated Create React App. It bundles your code, starts a lightning-fast dev server, and hot-reloads the browser every time you save a file.

Clean up scaffold boilerplate and add project styles

The Vite scaffold ships with a demo page full of logos, a counter, and community links. You need to strip all of that out and replace it with a clean starting point for the asset manager.

  • In the VS Code Explorer sidebar on the left, expand the src folder and click App.jsx to open it.
  • Select all the existing code with Cmd+A (macOS) or Ctrl+A (Windows) and delete it.
  • Replace the contents of App.jsx with this code:
function App() {
  return (
    <h1>Asset Manager</h1>
  )
}

export default App

What does this code do?

This defines a React component called App that returns a single <h1> heading. React components are JavaScript functions that return JSX, which looks like HTML but lives inside your JavaScript files.

  • Save App.jsx with Cmd+S (macOS) or Ctrl+S (Windows).
  • Now open App.css in the src folder.
  • Select all the existing styles with Cmd+A (macOS) or Ctrl+A (Windows) and delete them so the file is completely empty.
  • Save App.css.

With the component and its styles cleaned up, start the dev server so you can see changes in real time as you work on the CSS next.

  • Open the VS Code integrated terminal by pressing Ctrl+` (backtick key, below Esc).
  • Start the development server by running:
npm run dev

The terminal will display a local URL. Vite keeps running in this terminal and watches for file changes.

  • Hold Cmd (macOS) or Ctrl (Windows) and click the http://localhost:5173 link in the terminal to open it in your browser.

You should see the "Asset Manager" heading, possibly with some leftover Vite scaffold styling. That is about to change once you replace index.css with the project's own styles.

Next you will replace the scaffold styles in index.css with the project's own CSS. Because the dev server is running, each time you save the file your browser will hot-reload and show the changes.

  • Click on index.css in the src folder to open it.
  • Select all the existing content with Cmd+A (macOS) or Ctrl+A (Windows) and delete it.
  • Paste in the base styles (CSS variables, reset, and body) by copying this code:
:root {
  --sidebar-width: 240px;
  --navbar-height: 60px;
  --color-primary: #4f46e5;
  --color-sidebar: #1e1b4b;
  --color-sidebar-hover: #312e81;
  --color-bg: #f1f5f9;
  --color-white: #ffffff;
  --color-text: #1e293b;
  --color-text-light: #64748b;
  --color-border: #e2e8f0;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  color: var(--color-text);
  background-color: var(--color-bg);
}

.app-layout {
  display: flex;
  min-height: 100vh;
}

What do these styles do?

The :root block defines CSS custom properties (variables) for colours, spacing, and widths used across the entire project. The * reset removes default browser margins and padding so every element starts from a clean baseline.

The body rule sets the background to a light gray (--color-bg) and applies the app font. .app-layout creates the top-level flex container that will hold the sidebar and main area side by side.

  • Save index.css and check your browser.

The page background should now be light gray, and the heading text should have changed to a dark sans-serif font. That confirms the CSS variables are working.

  • Directly below the code you just pasted, add the sidebar styles:
.sidebar {
  width: var(--sidebar-width);
  background-color: var(--color-sidebar);
  color: var(--color-white);
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
}

.sidebar-header {
  padding: 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.sidebar-header h2 {
  font-size: 1.2rem;
  font-weight: 600;
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  padding: 12px;
  gap: 4px;
}
  • Below the sidebar styles, add the navigation link and main area styles:
.nav-link {
  display: block;
  padding: 10px 16px;
  color: rgba(255, 255, 255, 0.7);
  text-decoration: none;
  border-radius: 6px;
  font-size: 0.95rem;
  transition: background-color 0.2s, color 0.2s;
}

.nav-link:hover {
  background-color: var(--color-sidebar-hover);
  color: var(--color-white);
}

.nav-link.active {
  background-color: var(--color-primary);
  color: var(--color-white);
}

.main-area {
  margin-left: var(--sidebar-width);
  flex: 1;
  display: flex;
  flex-direction: column;
}
  • Save index.css and glance at your browser.

The page still looks the same because no component uses these class names yet. The sidebar and nav-link styles prepare the layout shell you will build in Step 3.

  • Below the main area rule, add the navbar and page content styles:
.navbar {
  height: var(--navbar-height);
  background-color: var(--color-white);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  position: sticky;
  top: 0;
  z-index: 10;
}

.navbar h1 {
  font-size: 1.1rem;
  font-weight: 600;
}

.navbar-user {
  font-size: 0.9rem;
  color: var(--color-text-light);
}

.page-content {
  padding: 24px;
  flex: 1;
}

.dashboard h2 {
  margin-bottom: 20px;
}
  • Below the dashboard heading rule, add the stat card styles:
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin-bottom: 32px;
}

.stat-card {
  background-color: var(--color-white);
  border-radius: 8px;
  padding: 20px;
  border: 1px solid var(--color-border);
}

.stat-card h3 {
  font-size: 0.85rem;
  color: var(--color-text-light);
  font-weight: 500;
  margin-bottom: 8px;
}

.stat-value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--color-text);
  margin-bottom: 4px;
}

.stat-description {
  font-size: 0.8rem;
  color: var(--color-text-light);
}
  • Save index.css to keep your progress.
  • Finally, add the chart section and placeholder page styles below:
.chart-section {
  background-color: var(--color-white);
  border-radius: 8px;
  padding: 20px;
  border: 1px solid var(--color-border);
}

.chart-section h3 {
  font-size: 1rem;
  margin-bottom: 16px;
}

.page-placeholder {
  background-color: var(--color-white);
  border-radius: 8px;
  padding: 40px;
  border: 1px solid var(--color-border);
  text-align: center;
}

.page-placeholder h2 {
  margin-bottom: 12px;
}

.page-placeholder p {
  color: var(--color-text-light);
}

What do the card, chart, and placeholder styles do?

The .stats-grid creates a responsive grid that arranges stat cards side by side. Each .stat-card gets a white background, rounded corners, and a subtle border.

The .chart-section wraps the bar chart you will build in Step 2. The .page-placeholder styles format the placeholder pages (Assets, Inventory, Tickets, Profile) that you will create in Step 4.

  • Save index.css. The file should end with the .page-placeholder p rule.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files: App.jsx, App.css, and index.css.

ⓧ I'd like to double check the full code

Scroll through index.css and confirm you have each of these sections in order:

  • :root variables, * reset, body, and .app-layout (the base styles chunk).
  • .sidebar, .sidebar-header, .sidebar-header h2, and .sidebar-nav (sidebar chunk).
  • .nav-link, .nav-link:hover, .nav-link.active, and .main-area (nav links chunk).
  • .navbar, .navbar h1, .navbar-user, .page-content, and .dashboard h2 (navbar chunk).
  • .stats-grid, .stat-card, .stat-card h3, .stat-value, and .stat-description (stat cards chunk).
  • .chart-section, .chart-section h3, .page-placeholder, .page-placeholder h2, and .page-placeholder p (chart and placeholder chunk).

For App.jsx, confirm it contains only this:

function App() {
  return (
    <h1>Asset Manager</h1>
  )
}

export default App

App.css should be completely empty.

Start the dev server and confirm it works

The dev server should still be running from earlier. If you stopped it, start it again now.

  • Switch back to your browser tab at http://localhost:5173.
  • Before you look, predict what you expect to see. Will the page show the old Vite demo, or your new heading?

You should see a page with just the "Asset Manager" heading on a light gray background. The gray comes from the --color-bg variable you set in index.css.

Still seeing the Vite demo page?

  • Make sure you saved all three files: App.jsx, App.css, and index.css. Unsaved changes will not appear in the browser.
  • Check that App.jsx returns only <h1>Asset Manager</h1> and does not still import the old scaffold assets.
  • Try a hard refresh in the browser with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows).
  • Help me fix my Vite dev server not showing updates.

Your development environment is ready and the app is running. Next up, you will build the Dashboard page with stat cards and a bar chart powered by mock data.

Build the Dashboard Page

Right now your browser shows a bare "Asset Manager" heading on a gray background. The dashboard is the centerpiece of this app, and building it first gives you something visual to work with immediately.

In this step, you will create mock data for the dashboard, build reusable React components that display stat cards, and render a bar chart using Recharts. Along the way, you will learn three core React patterns: creating components, passing data through props, and rendering lists with .map().

In this step, get ready to:
  • Create mock data that drives the dashboard stat cards and chart.
  • Build a reusable StatCard component that accepts props.
  • Render an interactive bar chart with Recharts.
Create the mock data

Before you build any components, you need data for them to display. In a production app this data would come from a backend API, but for now you will use a local JavaScript file with hardcoded arrays.

  • In the VS Code Explorer sidebar, right-click the src folder and select New Folder.
  • Name the folder data.
  • Right-click the new data folder and select New File.
  • Name the file mockData.js.
  • Paste the following code into mockData.js:
// Summary statistics for the dashboard cards
export const mockStats = [
  { title: 'Total Assets', value: 342, description: 'Across all departments' },
  { title: 'Open Tickets', value: 27, description: '5 high priority' },
  { title: 'Inventory Items', value: 1205, description: '98% in stock' },
  { title: 'Active Users', value: 56, description: '12 online now' },
]

// Monthly data points for the bar chart
export const mockChartData = [
  { month: 'Jan', assets: 30 },
  { month: 'Feb', assets: 45 },
  { month: 'Mar', assets: 28 },
  { month: 'Apr', assets: 52 },
  { month: 'May', assets: 38 },
  { month: 'Jun', assets: 65 },
]

What does this code do?

This file exports two arrays that the dashboard will consume.

  • mockStats holds four objects, each with a title, value, and description. Each object powers one stat card on the dashboard.
  • mockChartData holds six monthly data points. Each has a month label and an assets count for the bar chart.
  • Save mockData.js.
  • Confirm the file appears in the Explorer sidebar under src/data/mockData.js.

Don't see the data folder?

Make sure you created the data folder inside src, not at the project root. The file path should be src/data/mockData.js.

Help me find my data folder

✔️ Awesome, I've got everything!

Great. Make sure you have saved the file before moving on.

ⓧ I'd like to double check the full code

export const mockStats = [
  { title: 'Total Assets', value: 342, description: 'Across all departments' },
  { title: 'Open Tickets', value: 27, description: '5 high priority' },
  { title: 'Inventory Items', value: 1205, description: '98% in stock' },
  { title: 'Active Users', value: 56, description: '12 online now' },
]

export const mockChartData = [
  { month: 'Jan', assets: 30 },
  { month: 'Feb', assets: 45 },
  { month: 'Mar', assets: 28 },
  { month: 'Apr', assets: 52 },
  { month: 'May', assets: 38 },
  { month: 'Jun', assets: 65 },
]
Build the stat cards

React apps are built from components, which are JavaScript functions that return JSX (HTML-like syntax). You will create a StatCard component that receives data through props, then a Dashboard component that loops through mockStats to render four cards.

  • In the Explorer sidebar, right-click the src folder and select New Folder.
  • Name the folder pages.
  • Right-click the new pages folder and select New File.
  • Name the file Dashboard.jsx.
  • Add the imports and the StatCard component by pasting this code into Dashboard.jsx:
import { mockStats } from '../data/mockData'

// Reusable card that displays one stat
function StatCard({ title, value, description }) {
  return (
    <div className="stat-card">
      <h3>{title}</h3>
      <p className="stat-value">{value}</p>
      <p className="stat-description">{description}</p>
    </div>
  )
}

What are props?

Props are the inputs a React component receives from its parent. The curly braces in { title, value, description } destructure the props object so you can use each value directly inside the JSX.

Every StatCard gets its own set of props, so the same component function renders four different cards with four different data sets.

  • Save Dashboard.jsx.
  • Check the VS Code Problems panel at the bottom of the editor. You should see no errors listed for Dashboard.jsx.

Seeing a red underline in Dashboard.jsx?

Make sure the import path is '../data/mockData' with two dots. The file lives one directory up from pages, then inside data.

Help me debug my Dashboard.jsx errors

  • Now paste the Dashboard component below the StatCard function in the same file:
// Main dashboard page rendering stat cards in a grid
function Dashboard() {
  return (
    <div className="dashboard">
      <h2>Dashboard</h2>
      <div className="stats-grid">
        {mockStats.map((stat) => (
          <StatCard
            key={stat.title}
            title={stat.title}
            value={stat.value}
            description={stat.description}
          />
        ))}
      </div>
    </div>
  )
}

export default Dashboard

What does .map() do here?

mockStats.map() loops through the four stat objects and returns a StatCard component for each one. React renders all four cards into the stats-grid div automatically.

The key={stat.title} prop gives each card a unique identifier so React can efficiently update the list if the data changes.

The Dashboard component is ready, but the app does not render it yet. You need to update App.jsx to import and display it.

  • Open src/App.jsx in the editor.
  • Replace the entire contents of the file with this code:
import Dashboard from './pages/Dashboard'

function App() {
  return (
    <Dashboard />
  )
}

export default App

What changed in App.jsx?

The old <h1>Asset Manager</h1> heading is gone. In its place, <Dashboard /> renders your new Dashboard component, which in turn renders the four stat cards.

  • Save both Dashboard.jsx and App.jsx.
  • Switch to your browser. You should see four stat cards in a grid: Total Assets (342), Open Tickets (27), Inventory Items (1205), and Active Users (56).

Don't see the stat cards?

Make sure you saved both files. Vite's dev server only picks up changes on save.

If the page is blank, check the browser console for errors. A common cause is a typo in the import path inside App.jsx. It should be './pages/Dashboard' with a leading dot-slash.

Help me debug my missing stat cards

✔️ Awesome, I've got everything!

Great. Double check you have saved both files before moving on.

ⓧ I'd like to double check the full code

Here is the complete App.jsx at this point:

import Dashboard from './pages/Dashboard'

function App() {
  return (
    <Dashboard />
  )
}

export default App

For Dashboard.jsx, scroll up and confirm you have these two sections in order:

  • The import line and the StatCard function (first chunk you pasted).
  • The Dashboard function and the export default Dashboard line (second chunk you pasted).
Add the bar chart

The stat cards show summary numbers, but a chart makes trends visible at a glance. Recharts is a React charting library that provides chart components you can drop straight into JSX.

  • In Dashboard.jsx, find the import line at the very top of the file:
import { mockStats } from '../data/mockData'
  • Replace that single line with these two import lines:
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'
import { mockStats, mockChartData } from '../data/mockData'

What are these new imports?

The first line pulls in the Recharts components you need to build a bar chart: the chart container, the bars, axes, a grid, and a hover tooltip.

The second line adds mockChartData alongside mockStats so the Dashboard has access to the monthly data points.

  • Now find the closing </div> of the stats-grid div. It is right after the )) line.
  • Paste this chart section directly below that closing </div> tag, but still inside the outer dashboard div:
      <div className="chart-section">
        <h3>Monthly Asset Acquisitions</h3>
        {/* Recharts bar chart with responsive sizing */}
        <BarChart
          responsive
          style={{ width: '100%', maxWidth: '700px', aspectRatio: 1.618 }}
          data={mockChartData}
        >
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="month" />
          <YAxis />
          <Tooltip />
          <Bar dataKey="assets" fill="#4f46e5" radius={[4, 4, 0, 0]} />
        </BarChart>
      </div>

What does this chart code do?

  • BarChart is the container. The responsive prop tells Recharts to size itself using CSS instead of fixed pixel dimensions.
  • data={mockChartData} feeds the six monthly data points into the chart.
  • XAxis dataKey="month" labels each bar with the month name. Bar dataKey="assets" determines the bar height from the assets value.
  • Tooltip shows the exact value when you hover over a bar.

Before you check the browser, what do you think you will see below the stat cards?

  • Save Dashboard.jsx.
  • Switch to your browser. You should see the four stat cards at the top, and below them a bar chart labeled "Monthly Asset Acquisitions" with six bars.
  • Hover over any bar. A tooltip appears showing the month name and the asset count for that month.

Chart not showing up?

Make sure both import lines are at the very top of Dashboard.jsx and that you added mockChartData to the second import.

Check that the chart code is inside the dashboard div but outside the stats-grid div. If it is nested inside stats-grid, the chart will render inside the card grid instead of below it.

Help me debug my missing bar chart

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before moving on.

ⓧ I'd like to double check the full code

Your Dashboard.jsx should have these sections in order from top to bottom:

  • Two import lines: one from recharts and one from ../data/mockData.
  • The StatCard function returning a stat-card div.
  • The Dashboard function containing a stats-grid div with the .map() call, followed by a chart-section div with the BarChart.
  • export default Dashboard at the bottom.

Your dashboard already looks like a real admin panel. Next up, you will wrap it in a proper app shell with a sidebar and navbar so it feels like a complete application.

Create the App Shell Layout

Your dashboard looks great, but it's floating alone on the page. A real admin panel has a sidebar for navigation and a top bar showing the app name.

In this step you will build a Layout component that wraps the dashboard with a dark sidebar and a white navbar. The sidebar will have navigation links built with regular HTML anchor tags. This works for now, but it reveals an important problem you will fix in the next step.

In this step, get ready to:
  • Create a Layout component with a sidebar and navbar.
  • Wrap the Dashboard inside the Layout.
  • Discover what happens when sidebar links use plain anchor tags.
Build the Layout component

The Layout component is a wrapper that places a sidebar on the left, a navbar on top, and renders whatever page content it receives as children in the main area.

  • In VS Code's Explorer sidebar, right-click the src folder.
  • Click New Folder and name it components.
  • Right-click the new components folder, click New File, and name it Layout.jsx.
  • Paste the following code into Layout.jsx:
function Layout({ children }) {
  return (
    <div className="app-layout">
      {/* Sidebar with navigation links */}
      <aside className="sidebar">
        <div className="sidebar-header">
          <h2>Asset Manager</h2>
        </div>
        <nav className="sidebar-nav">
          <a href="/" className="nav-link">Dashboard</a>
          <a href="/assets" className="nav-link">Assets</a>
          <a href="/inventory" className="nav-link">Inventory</a>
          <a href="/tickets" className="nav-link">Tickets</a>
          <a href="/profile" className="nav-link">Profile</a>
        </nav>
      </aside>
      {/* Main area with top navbar and page content */}
      <div className="main-area">
        <header className="navbar">
          <h1>IT Asset Management</h1>
          <span className="navbar-user">Admin</span>
        </header>
        <main className="page-content">
          {children}
        </main>
      </div>
    </div>
  )
}
export default Layout

What does this code do?

The Layout function accepts a children prop. This is a special React prop that represents whatever you nest inside the component's opening and closing tags.

  • The <aside> element renders the dark sidebar with five navigation links, each pointing to a different URL path like /assets or /tickets.
  • The main-area div holds the top navbar and a <main> section where {children} renders whatever page component you pass in.
  • The links use plain <a> HTML anchor tags. These are the standard way to link between pages in regular HTML.

✔️ Awesome, I've got everything!

Great. Make sure you have saved Layout.jsx before moving on.

ⓧ I'd like to double check the full code

Here is the complete src/components/Layout.jsx file. Make sure yours matches exactly.

function Layout({ children }) {
  return (
    <div className="app-layout">
      <aside className="sidebar">
        <div className="sidebar-header">
          <h2>Asset Manager</h2>
        </div>
        <nav className="sidebar-nav">
          <a href="/" className="nav-link">Dashboard</a>
          <a href="/assets" className="nav-link">Assets</a>
          <a href="/inventory" className="nav-link">Inventory</a>
          <a href="/tickets" className="nav-link">Tickets</a>
          <a href="/profile" className="nav-link">Profile</a>
        </nav>
      </aside>
      <div className="main-area">
        <header className="navbar">
          <h1>IT Asset Management</h1>
          <span className="navbar-user">Admin</span>
        </header>
        <main className="page-content">
          {children}
        </main>
      </div>
    </div>
  )
}

export default Layout
Wrap the Dashboard inside the Layout

The Layout component exists now, but nothing uses it yet. You need to import it into App.jsx and wrap the Dashboard inside it.

  • Switch back to src/App.jsx in VS Code.
  • Find the existing import at the top of the file:
import Dashboard from './pages/Dashboard'
  • Replace the entire contents of App.jsx with this updated code:
import Layout from './components/Layout'
import Dashboard from './pages/Dashboard'

function App() {
  return (
    // Layout adds the sidebar and navbar around Dashboard
    <Layout>
      <Dashboard />
    </Layout>
  )
}

export default App

What changed here?

  • You added import Layout from './components/Layout' so App knows about the new component.
  • Instead of rendering <Dashboard /> alone, you now nest it inside <Layout>. React passes <Dashboard /> as the children prop to Layout automatically.
  • Save both Layout.jsx and App.jsx.
  • Return to your browser. You should see a dark indigo sidebar on the left with "Asset Manager" at the top and five navigation links below it. A white navbar reading "IT Asset Management" sits across the top, and your stat cards and bar chart fill the main content area on the right.

Sidebar not showing up?

  • Make sure you saved both files. Unsaved changes won't trigger a hot reload.
  • Check that the import path in App.jsx says ./components/Layout. A typo in the folder or file name causes an import error.
  • Confirm that Layout.jsx is inside src/components/ and not directly in src/.

Still stuck? Ask for help.

✔️ Awesome, I've got everything!

Great. Double check you have saved App.jsx.

ⓧ I'd like to double check the full code

Here is the complete src/App.jsx file. Make sure yours matches exactly.

import Layout from './components/Layout'
import Dashboard from './pages/Dashboard'

function App() {
  return (
    <Layout>
      <Dashboard />
    </Layout>
  )
}

export default App
Test the sidebar links

The sidebar looks great. Before you move on, try clicking one of those navigation links to see what happens.

Before you click, make a prediction: what do you think will happen when you click Assets in the sidebar? Will the dashboard swap out for an Assets page, or will something else happen?

  • Click Assets in the sidebar.

The page does a full browser reload and you land on a blank white page. The sidebar, navbar, and dashboard are all gone.

Why did the page go blank?

The sidebar links are plain HTML <a> anchor tags. When you click one, the browser does what it always does with anchor tags: it sends a brand new request to the server for /assets.

Vite's dev server only serves your app at the root / path. There is no separate page at /assets, so the browser shows a blank page. This full reload also destroys the entire React app, wiping out the sidebar, navbar, and everything else.

This is the core problem with regular anchor tags in a React app. You need a way to swap out the page content without triggering a full browser reload.

  • Click your browser's back button to return to the dashboard.

You have uncovered the exact problem that React Router was built to solve. In the next step, you will replace those anchor tags with React Router's special link components that swap page content instantly, with no reload.

Wire Up Client-Side Routing

Your sidebar and navbar give the app a professional admin-panel structure. The problem is that every sidebar link causes a full browser reload and lands on a blank white page.

React Router fixes this by intercepting link clicks and swapping components on the client side, all without a page reload. In this step, you will wire up five routes and replace those broken anchor tags with React Router's navigation components.

What is client-side routing?

Traditional websites send a new request to the server every time you click a link. The browser loads a brand-new HTML page, which causes a visible flash and resets all JavaScript state.

Client-side routing skips the server round-trip entirely. A library like React Router watches for link clicks, updates the URL in the address bar, and swaps the rendered component. The page never reloads.

In this step, get ready to:
  • Set up BrowserRouter and define routes for all five pages.
  • Create placeholder page components for Assets, Inventory, Tickets, and Profile.
  • Replace anchor tags with NavLink for instant navigation and active-link highlighting.
Wrap the app in BrowserRouter and define routes

React Router needs two things to work. First, a BrowserRouter component wrapping your entire app so every component has access to the router. Second, a set of Route definitions that map URL paths to components.

  • Replace the entire contents of src/main.jsx with this code to add BrowserRouter:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
// BrowserRouter enables client-side routing for the entire app
import { BrowserRouter } from 'react-router'
import App from './App.jsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
)

What does this code do?

BrowserRouter wraps <App /> so every component inside your app can use React Router's navigation features. Without this wrapper, route definitions and link components will not function.

  • Save src/main.jsx.
  • Check your browser. The app should still load normally with the sidebar and dashboard visible.

Now you need to define which component renders at each URL. The route structure uses a parent route for the layout and nested child routes for each page.

  • Replace the entire contents of src/App.jsx with this route configuration:
import { Routes, Route } from 'react-router'
import Layout from './components/Layout'
import Dashboard from './pages/Dashboard'
import Assets from './pages/Assets'
import Inventory from './pages/Inventory'
import Tickets from './pages/Tickets'
import Profile from './pages/Profile'

function App() {
  return (
    // Routes container holds all route definitions
    <Routes>
      {/* Parent route renders Layout on every page */}
      <Route element={<Layout />}>
        {/* index makes Dashboard the default at / */}
        <Route index element={<Dashboard />} />
        <Route path="assets" element={<Assets />} />
        <Route path="inventory" element={<Inventory />} />
        <Route path="tickets" element={<Tickets />} />
        <Route path="profile" element={<Profile />} />
      </Route>
    </Routes>
  )
}

export default App

What does this code do?

  • Routes is the container that holds all your route definitions.
  • The outer Route with element={<Layout />} is a parent route. Layout renders on every page, and child routes render inside it.
  • Route index makes Dashboard the default page at /. Each other child route maps a URL path like /assets to its page component.
  • Save src/App.jsx.
  • Your browser will show an error because the imported page files (Assets.jsx, Inventory.jsx, etc.) do not exist yet. This is expected. You will create them next.
Create placeholder page components

The route definitions in App.jsx point to four page components that do not exist yet. Each placeholder will have a heading and a short description so you can confirm the routes work.

  • Create a new file called Assets.jsx inside the src/pages/ folder with this code:
// Placeholder - asset list and modals come in the next project
function Assets() {
  return (
    <div className="page-placeholder">
      <h2>Assets</h2>
      <p>Asset list with add/edit modals will be built in the next project.</p>
    </div>
  )
}

export default Assets
  • Create Inventory.jsx in src/pages/ with this code:
// Placeholder - inventory management comes in the next project
function Inventory() {
  return (
    <div className="page-placeholder">
      <h2>Inventory</h2>
      <p>Inventory management page will be built in the next project.</p>
    </div>
  )
}

export default Inventory
  • Create Tickets.jsx in src/pages/ with this code:
// Placeholder - ticket tracking comes in a future project
function Tickets() {
  return (
    <div className="page-placeholder">
      <h2>Tickets</h2>
      <p>Ticket creation and tracking will be built in a future project.</p>
    </div>
  )
}

export default Tickets
  • Create Profile.jsx in src/pages/ with this code:
// Placeholder - user profile comes in a future project
function Profile() {
  return (
    <div className="page-placeholder">
      <h2>Profile</h2>
      <p>User profile page will be built in a future project.</p>
    </div>
  )
}

export default Profile

What do these components do?

Each component returns a styled div with a heading and description. The page-placeholder class you already defined in index.css gives them a centered, card-like appearance. These pages are ready for real content in future projects.

  • Save all four files.
  • Check your browser. The error should be gone and the sidebar and navbar should appear. The main content area will be empty though.

Why is the main content area empty?

Layout.jsx still renders {children}, which was the old way of passing content into it. React Router uses a different mechanism called Outlet to display child routes. You will swap that out in the next substep.

Still seeing an error?

  • Make sure each file name matches the import in App.jsx exactly. Assets.jsx must start with a capital A.
  • Confirm all four files are inside the src/pages/ folder, not in src/ or src/components/.
  • Check that each file has export default at the bottom. A missing export causes a blank import.

Why is my app still showing an error after creating the page files?

Replace anchor tags with NavLink

The final piece is updating Layout.jsx. React Router provides NavLink to replace standard <a> tags with links that navigate without reloading. It also provides Outlet as the spot where child route components render.

  • Clear all existing code in src/components/Layout.jsx and paste this code for the import and sidebar navigation:
import { NavLink, Outlet } from 'react-router'

function Layout() {
  return (
    <div className="app-layout">
      {/* Sidebar with NavLink for client-side navigation */}
      <aside className="sidebar">
        <div className="sidebar-header">
          <h2>Asset Manager</h2>
        </div>
        <nav className="sidebar-nav">
          {/* end prop ensures this only highlights at exactly / */}
          <NavLink to="/" end className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
            Dashboard
          </NavLink>
          <NavLink to="/assets" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
            Assets
          </NavLink>
          <NavLink to="/inventory" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
            Inventory
          </NavLink>
          <NavLink to="/tickets" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
            Tickets
          </NavLink>
          <NavLink to="/profile" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}>
            Profile
          </NavLink>
        </nav>
      </aside>

What does the NavLink className callback do?

NavLink passes an isActive boolean to its className callback. When the current URL matches the link's to prop, isActive is true and the callback returns nav-link active. Your CSS already styles .nav-link.active with a purple background.

The end prop on the Dashboard NavLink prevents it from matching every URL. Without it, / would match /assets, /inventory, and every other path, keeping Dashboard permanently highlighted.

  • Right below the code you just pasted, add the main content area and close the component:
      {/* Main content area with navbar and Outlet for child routes */}
      <div className="main-area">
        <header className="navbar">
          <h1>IT Asset Management</h1>
          <span className="navbar-user">Admin</span>
        </header>
        <main className="page-content">
          {/* Outlet renders whichever child route matches the URL */}
          <Outlet />
        </main>
      </div>
    </div>
  )
}

export default Layout

What does Outlet do?

Outlet is React Router's placeholder for child routes. When the URL is /assets, the Outlet renders the Assets component. When it is /, it renders Dashboard. The sidebar and navbar stay put because they live outside the Outlet.

  • Save src/components/Layout.jsx.

Before you click a sidebar link, what do you think will happen this time compared to the full-page reloads you saw before?

  • Click Assets in the sidebar.

The URL changes to /assets and the main area shows "Assets" with the placeholder text. No page reload. No white flash.

  • Click every other sidebar link (Inventory, Tickets, Profile) and confirm each one swaps the main content instantly.
  • Click Dashboard to return to the home page. The stat cards and bar chart should still display correctly.
  • Watch the sidebar as you navigate. The active link highlights in purple, and previously active links return to their default color.

Clicking a link still causes a full reload?

  • Make sure you replaced every <a href> tag with a <NavLink to> tag in Layout.jsx. Even one leftover <a> tag will reload the page when clicked.
  • Check that BrowserRouter wraps <App /> in src/main.jsx. Without it, NavLink falls back to standard browser navigation.
  • Confirm the import at the top of Layout.jsx says from 'react-router' and not from 'react-router-dom'. React Router v8 removed the separate react-router-dom package.

Why are my sidebar links still causing full page reloads?

✔️ Awesome, I've got everything!

Great. Double check you have saved all your files.

ⓧ I'd like to double check the full code

The full Layout.jsx is built from the two chunks above. Scroll up and confirm you have both the sidebar section (ending with </aside>) and the main area section (ending with export default Layout) pasted in order.

Here is src/main.jsx for reference:

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router'
import App from './App.jsx'
import './index.css'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
)

Here is src/App.jsx for reference:

import { Routes, Route } from 'react-router'
import Layout from './components/Layout'
import Dashboard from './pages/Dashboard'
import Assets from './pages/Assets'
import Inventory from './pages/Inventory'
import Tickets from './pages/Tickets'
import Profile from './pages/Profile'

function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route index element={<Dashboard />} />
        <Route path="assets" element={<Assets />} />
        <Route path="inventory" element={<Inventory />} />
        <Route path="tickets" element={<Tickets />} />
        <Route path="profile" element={<Profile />} />
      </Route>
    </Routes>
  )
}

export default App

Your app now navigates like a real admin panel. Every sidebar click swaps the page content instantly, and the active link lights up so you always know where you are. Next up, there is an optional Secret Mission to make this layout responsive for mobile screens.

Secret mission

Make It Responsive

The layout works great on desktop, but on a narrow screen the sidebar eats up too much space. Add a mobile-responsive sidebar that hides by default on small screens and toggles open with a hamburger menu button.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing cloud costs or subscriptions to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The asset-manager project folder on your Desktop.
  • The Vite dev server process running in your terminal.

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue to the next project in the series.

  • Your Vite dev server stays running at http://localhost:5173.
  • Your project files remain in the asset-manager folder on your Desktop, ready for the next project.

Pause - I'll come back to this later

Shut down the running dev server to free up your terminal and the port, but keep all your project files intact.

  • Switch to the terminal where your Vite dev server is running.
  • Press Ctrl+C to stop the server.
  • Your asset-manager folder stays on your Desktop. When you are ready to resume, open the folder in VS Code, open the terminal, and run npm run dev to start the server again.

Delete - I don't want to use this again

Remove all project files and start fresh if you ever want to redo the project.

  • Switch to the terminal where your Vite dev server is running.
  • Press Ctrl+C to stop the server.
  • Delete the project folder by running this command (macOS/Linux):
cd ~/Desktop
rm -rf asset-manager
  • On Windows, run this instead:
cd ~\Desktop
rmdir /s /q asset-manager
  • Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see asset-manager in the output.

Nice Work!

Nice Work!

You just built a fully navigable IT Asset Management dashboard from scratch with React. Your app has stat cards, a live bar chart, a sidebar with active link highlighting, and instant page transitions. That is a real admin panel running on your machine.

What you learned:

  • Built reusable React components (StatCard, Layout) that accept props and composed them into a Dashboard page with mock data, summary cards, and a Recharts bar chart.
  • Discovered why plain anchor tags cause full page reloads in a single-page app and replaced them with React Router's NavLink for instant client-side routing.
  • Set up 5 routes with a persistent sidebar and navbar layout using Outlet, scaffolded with Vite and powered by the React Router declarative mode.
  • Secret Mission: Completed an optional challenge to push your React skills further.

Ready to quiz yourself?