Dashboard UI Project

Build a responsive analytics dashboard with React, Recharts, dark mode, filters, reusable widgets, and real-world component patterns

Time to implement the project: ~ 28-40 hours

  • React Components
  • Recharts
  • Dashboard Layout
  • Responsive Sidebar
  • Dark Mode
  • Data Filtering
  • Reusable UI Patterns
  • State Management

In this intermediate React project, you will build a responsive analytics dashboard that presents business data through charts, metric cards, filters, navigation sections, and a modern application-style layout. The dashboard should include a sidebar, top area, statistics widgets, chart sections built with Recharts, filter controls, and a dark mode switch that changes the visual theme without breaking readability or layout consistency.

This project is designed to feel closer to real product work than a simple landing page or isolated component. Dashboards require careful thinking about layout structure, reusable components, data flow, responsive behavior, visual hierarchy, and interface states. You will practice building a React UI that can organize many pieces of information while still remaining clean, readable, and easy to extend.

Project Goal and Professional Learning Value

The main goal of this project is to teach you how to structure a real-world dashboard interface in React. Analytics dashboards are common in admin panels, SaaS products, marketing tools, finance platforms, CRM systems, internal company tools, and e-commerce management systems. They usually combine navigation, data summaries, charts, filters, tables, responsive layouts, and user preferences in one interface.

You will learn how to split a complex screen into smaller reusable components. Instead of building one large dashboard component, you should create separate parts such as Sidebar, Header, StatCard, ChartCard, FilterBar, ThemeToggle, DataTable, and DashboardSection. This helps you practice component composition, props, reusable patterns, and cleaner project architecture.

The project also teaches an important frontend skill: presenting data in a way that is both useful and understandable. You will not only render charts, but also decide how filters affect displayed results, how metric cards should update, how the interface behaves in dark mode, and how the layout adapts when screen space becomes limited. This is the type of thinking expected in practical React work.

Recommended Knowledge Before You Start

This is an intermediate-level React project, so you should already understand components, props, state, event handling, conditional rendering, and basic array methods such as map, filter, reduce, and sort. You do not need a real backend for the first version. The dashboard can use local mock data stored in JavaScript files, but the data should be structured realistically enough to support filtering and chart updates.

You should also be comfortable working with CSS layouts. A dashboard is not just a collection of boxes. It needs a sidebar that behaves differently on desktop and mobile, a content area that remains readable, chart containers that resize correctly, and spacing that works in both light and dark themes. Strong layout discipline is essential for making the project feel professional.

  • Solid understanding of React components, props, state, and conditional rendering
  • Ability to structure reusable UI components instead of placing everything inside one file
  • Basic experience with installing and using third-party libraries such as Recharts
  • Comfort with JavaScript array methods for filtering, grouping, counting, and transforming data
  • Understanding of responsive layouts, CSS Grid, Flexbox, media queries, and sidebar behavior
  • Basic knowledge of theme switching, CSS variables, localStorage, or class-based dark mode strategies

Core Features of the Analytics Dashboard

The dashboard should behave like a simplified product analytics interface. Users should be able to view key metrics, inspect chart trends, filter displayed data, switch between light and dark mode, and navigate the layout comfortably on different screen sizes. The goal is not to create dozens of pages, but to build one strong dashboard screen with realistic component behavior.

Feature Implementation Focus
Responsive dashboard layout Create a full application-style layout with sidebar navigation, header area, main content section, and dashboard cards. The layout should adapt cleanly across desktop, tablet, and mobile screens.
Sidebar navigation Build a responsive sidebar with navigation items, active states, icons or labels, and mobile behavior. On smaller screens, it can collapse, slide in, or become a compact menu.
Metric cards Display important numbers such as revenue, users, conversion rate, orders, traffic, or growth. Each card should have a label, value, comparison indicator, and clear visual hierarchy.
Charts with Recharts Use Recharts to create visual sections such as line charts, bar charts, area charts, or pie charts. Charts should be placed inside reusable containers and remain responsive.
Data filtering Add filters for date range, category, traffic source, product type, or region. When filters change, the visible metrics and chart data should update based on selected values.
Dark mode support Implement a theme switch that changes background, text, borders, cards, and chart readability. The selected theme can be saved so the user preference persists after refresh.
Reusable dashboard components Build components that can be reused with different data, such as StatCard, ChartPanel, FilterSelect, SidebarItem, ThemeToggle, and SectionHeader.
Empty and edge states Handle cases where filters produce no matching data, charts have too little data to display, or values are unavailable. The interface should explain the state instead of breaking.

Implementation Guidance for Intermediate React Developers

Start by designing the data structure before building the visual interface. A dashboard becomes much easier to manage when mock data is organized clearly, for example by date, category, source, value, revenue, users, orders, or conversion rate. If the data is poorly structured, filters and charts will quickly become messy and difficult to maintain.

Build the layout in layers. First create the main shell with sidebar, header, and content area. Then add metric cards. After that, add charts and filtering logic. Finally, implement dark mode and polish responsive behavior. This order prevents the project from becoming chaotic and helps you test each part before adding more complexity.

Pay special attention to component boundaries. The dashboard container can own selected filters and processed data, while smaller components should focus on rendering. For example, a ChartPanel should not know how all filters work; it should simply receive prepared data and display it. This makes the project closer to professional React architecture and easier to extend later.

  • Keep mock analytics data in a separate file and structure it like real product data
  • Create reusable cards and chart containers instead of duplicating layout markup
  • Use derived data from filters rather than manually editing multiple state variables
  • Make Recharts containers responsive so charts do not overflow on smaller screens
  • Use CSS variables or a theme class to keep dark mode consistent across the dashboard
  • Persist the selected theme in localStorage if you want a more realistic user experience
  • Test filter combinations that return normal data, limited data, and no matching results
  • Keep sidebar behavior simple, predictable, and comfortable for touch screens

Common Mistakes When Building a Dashboard UI Project

1. Hardcoding dashboard cards instead of creating a reusable metric model

A dashboard usually starts with a few visual cards: revenue, users, orders, projects, traffic, or notifications. A common mistake is hardcoding every card directly inside the page component. This works for a static screenshot, but it becomes difficult to maintain when the number of cards grows, the labels change, or the same card style is reused across several dashboard pages.

A stronger approach is to create a reusable metric model and render cards from data. This keeps the UI consistent and makes it easier to add icons, trends, loading states, and comparison values later. In admin dashboards, many cards follow the same structure: title, value, trend, description, icon, and link to a deeper page. That repeated structure should become a component, not duplicated markup.

Problematic approach:


          function Dashboard() {
            return (
              <div className="dashboard-grid">
                <div className="card">
                  <h3>Users</h3>
                  <p>12,430</p>
                  <span>+12%</span>
                </div>

                <div className="card">
                  <h3>Revenue</h3>
                  <p>$48,900</p>
                  <span>+8%</span>
                </div>

                <div className="card">
                  <h3>Orders</h3>
                  <p>1,284</p>
                  <span>-3%</span>
                </div>
              </div>
            );
          }

This markup is repetitive. If the card layout changes, every card has to be edited manually.

Better data-driven model:


          type MetricTrend = "positive" | "negative" | "neutral";

          type DashboardMetric = {
            id: string;
            title: string;
            value: string;
            changeLabel: string;
            trend: MetricTrend;
            description: string;
          };

          const dashboardMetrics: DashboardMetric[] = [
            {
              id: "users",
              title: "Users",
              value: "12,430",
              changeLabel: "+12%",
              trend: "positive",
              description: "Compared with last month"
            },
            {
              id: "revenue",
              title: "Revenue",
              value: "$48,900",
              changeLabel: "+8%",
              trend: "positive",
              description: "Monthly recurring revenue"
            },
            {
              id: "orders",
              title: "Orders",
              value: "1,284",
              changeLabel: "-3%",
              trend: "negative",
              description: "Compared with last week"
            }
          ];

Reusable card component:


          function MetricCard({ metric }: { metric: DashboardMetric }) {
            return (
              <Card className="metric-card">
                <CardContent>
                  <Typography variant="body2" color="text.secondary">
                    {metric.title}
                  </Typography>

                  <Typography variant="h4">
                    {metric.value}
                  </Typography>

                  <span className={`metric-card__trend metric-card__trend--${metric.trend}`}>
                    {metric.changeLabel}
                  </span>

                  <Typography variant="caption" color="text.secondary">
                    {metric.description}
                  </Typography>
                </CardContent>
              </Card>
            );
          }

Rendering the grid:


          function DashboardMetricGrid() {
            return (
              <section className="dashboard-metrics" aria-label="Dashboard metrics">
                {dashboardMetrics.map((metric) => (
                  <MetricCard key={metric.id} metric={metric} />
                ))}
              </section>
            );
          }

Pay attention to: Dashboard cards are repeated UI patterns. Create a clear metric model and reusable card component instead of hardcoding every widget in the page.

2. Building a sidebar that works on desktop but fails on mobile

Dashboards often depend on a sidebar for navigation. On desktop, the sidebar can stay visible. On tablets and phones, it usually needs to collapse into a drawer or overlay. A common mistake is building the sidebar as a fixed block with a fixed width and assuming the content area will always have enough space. This creates horizontal scrolling, hidden content, and poor mobile usability.

A good dashboard layout should treat navigation state as part of the UI architecture. The sidebar needs open and closed states, keyboard-accessible controls, clear active links, and responsive behavior. The main content area should adapt when the sidebar is collapsed. If you use Material UI, this is a strong place to practice combining AppBar, Drawer, Toolbar, routing, and theme-aware styling.

Problematic layout:


          .dashboard {
            display: flex;
          }

          .sidebar {
            width: 280px;
            height: 100vh;
            position: fixed;
          }

          .main {
            margin-left: 280px;
          }

This may work on a large screen, but on a small screen the sidebar can cover content or create horizontal overflow.

Better sidebar state:


          type LayoutState = {
            sidebarOpen: boolean;
          };

          function DashboardLayout() {
            const [sidebarOpen, setSidebarOpen] = useState(false);

            function toggleSidebar() {
              setSidebarOpen((isOpen) => !isOpen);
            }

            function closeSidebar() {
              setSidebarOpen(false);
            }

            return (
              <Box className="dashboard-layout">
                <AppBar position="sticky">
                  <Toolbar>
                    <IconButton
                      edge="start"
                      color="inherit"
                      onClick={toggleSidebar}
                      aria-label={sidebarOpen ? "Close navigation" : "Open navigation"}
                    >
                      <MenuIcon />
                    </IconButton>

                    <Typography variant="h6">Dashboard</Typography>
                  </Toolbar>
                </AppBar>

                <DashboardSidebar open={sidebarOpen} onClose={closeSidebar} />

                <Box component="main" className="dashboard-layout__content">
                  <Outlet />
                </Box>
              </Box>
            );
          }

Responsive CSS idea:


          .dashboard-layout {
            min-height: 100vh;
            display: grid;
            grid-template-rows: auto 1fr;
          }

          .dashboard-layout__content {
            padding: 24px;
            min-width: 0;
          }

          @media (min-width: 1024px) {
            .dashboard-layout {
              grid-template-columns: 280px minmax(0, 1fr);
            }

            .dashboard-layout__content {
              grid-column: 2;
            }
          }

Pay attention to: A dashboard sidebar is not just a visual column. It needs responsive behavior, active route states, accessible toggle controls, and a main content area that does not overflow on smaller screens.

3. Feeding charts with unprepared or incorrectly typed data

Charts are one of the main reasons dashboards exist, but they are often implemented too quickly. A common mistake is passing raw API data directly into a chart component and hoping the chart library will handle everything. This can produce wrong labels, broken tooltips, inconsistent date ordering, or charts that fail when a value is missing.

Chart components should receive prepared chart data, not raw business records. For example, if the dashboard has revenue records, the chart should receive clean labels and numeric values. Dates should be sorted. Missing values should be normalized. Display formatting should happen in tooltips or axis formatters, not inside the source numbers. This is especially important when using libraries such as Recharts, where small data-shape mistakes can lead to confusing visuals.

Problematic approach:


          const revenue = [
            { date: "2026-06-10", total: "$1,250" },
            { date: "2026-06-09", total: "$980" }
          ];

          <LineChart data={revenue}>
            <Line dataKey="total" />
          </LineChart>

The chart receives formatted strings instead of numeric values. It may not render correctly, and the dates are not sorted.

Better chart data model:


          type RevenueRecord = {
            date: string;
            totalCents: number;
          };

          type RevenueChartPoint = {
            label: string;
            revenue: number;
          };

          function createRevenueChartData(
            records: RevenueRecord[]
          ): RevenueChartPoint[] {
            return [...records]
              .sort((first, second) => {
                return new Date(first.date).getTime() - new Date(second.date).getTime();
              })
              .map((record) => {
                return {
                  label: formatShortDate(record.date),
                  revenue: record.totalCents / 100
                };
              });
          }

Chart rendering:


          const chartData = createRevenueChartData(revenueRecords);

          <ResponsiveContainer width="100%" height={320}>
            <LineChart data={chartData}>
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis dataKey="label" />
              <YAxis />
              <Tooltip
                formatter={(value) => {
                  return formatCurrency(Number(value));
                }}
              />
              <Line
                type="monotone"
                dataKey="revenue"
                strokeWidth={2}
                dot={false}
              />
            </LineChart>
          </ResponsiveContainer>

Empty chart state:


          if (!chartData.length) {
            return (
              <Card>
                <CardContent>
                  <Typography>No revenue data available yet.</Typography>
                </CardContent>
              </Card>
            );
          }

Pay attention to: Prepare chart data before rendering. Sort dates, keep values numeric, format only at display time, and add empty states for charts with no data.

4. Creating data tables without loading, empty, filtering, and pagination states

Admin dashboards usually include tables for users, projects, invoices, logs, orders, or contacts. A common mistake is rendering a table only for the perfect case where data exists and loads instantly. Real dashboards need loading states, empty states, error states, filters, sorting, and pagination. Without these states, the table feels broken as soon as data is delayed or the user applies a filter that returns no records.

A good dashboard table should communicate what is happening. If data is loading, show skeleton rows or a loading indicator. If there are no users, show a helpful empty state. If a search filter returns no matches, explain that the filter is the reason. If data fails to load, show an error and a retry action. This makes the UI feel like a real admin tool instead of a static template.

Problematic table:


          function UsersTable({ users }) {
            return (
              <Table>
                <TableBody>
                  {users.map((user) => (
                    <TableRow key={user.id}>
                      <TableCell>{user.name}</TableCell>
                      <TableCell>{user.email}</TableCell>
                      <TableCell>{user.role}</TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            );
          }

This assumes users is always ready and never empty. It also does not support filtering or pagination.

Better table state model:


          type TableState<T> =
            | { status: "loading" }
            | { status: "success"; data: T[] }
            | { status: "empty" }
            | { status: "error"; message: string };

          type UserFilters = {
            search: string;
            role: "all" | "admin" | "editor" | "viewer";
          };

Filtered data:


          function filterUsers(users: User[], filters: UserFilters): User[] {
            return users.filter((user) => {
              const matchesSearch =
                user.name.toLowerCase().includes(filters.search.toLowerCase()) ||
                user.email.toLowerCase().includes(filters.search.toLowerCase());

              const matchesRole =
                filters.role === "all" || user.role === filters.role;

              return matchesSearch && matchesRole;
            });
          }

State-aware rendering:


          function UsersTable({ state, filters }: {
            state: TableState<User>;
            filters: UserFilters;
          }) {
            if (state.status === "loading") {
              return <TableSkeleton rows={6} />;
            }

            if (state.status === "error") {
              return <ErrorState message={state.message} />;
            }

            if (state.status === "empty") {
              return <EmptyState title="No users yet" />;
            }

            const visibleUsers = filterUsers(state.data, filters);

            if (!visibleUsers.length) {
              return <EmptyState title="No users match these filters" />;
            }

            return <UserRows users={visibleUsers} />;
          }

Pay attention to: Tables need real states. Loading, empty, error, filtering, sorting, and pagination are not optional details in a dashboard UI.

5. Implementing light and dark mode with scattered CSS overrides

Many dashboard templates include light and dark mode. A common mistake is adding dark-mode styles as scattered CSS overrides across every component. This works at first, but it becomes difficult to maintain because each new page needs another set of manual color fixes. In a Material UI dashboard, theme values should be centralized in the MUI theme configuration, not repeated across random components.

A clean theme system should define palette values, background colors, text colors, card surfaces, border colors, and chart colors in one place. The theme mode should be stored in a predictable state and optionally persisted in localStorage. Components should use theme tokens instead of hardcoded colors. This keeps the dashboard consistent and makes future customization easier.

Problematic approach:


          function DashboardCard() {
            const darkMode = useDarkMode();

            return (
              <div
                style={{
                  background: darkMode ? "#111" : "#fff",
                  color: darkMode ? "#fff" : "#222",
                  border: darkMode ? "1px solid #333" : "1px solid #ddd"
                }}
              >
                Card content
              </div>
            );
          }

This forces every component to know the theme logic and repeat color values manually.

Better MUI theme setup:


          function createAppTheme(mode: "light" | "dark") {
            return createTheme({
              palette: {
                mode,
                background: {
                  default: mode === "dark" ? "#0f172a" : "#f8fafc",
                  paper: mode === "dark" ? "#111827" : "#ffffff"
                },
                text: {
                  primary: mode === "dark" ? "#f9fafb" : "#111827",
                  secondary: mode === "dark" ? "#cbd5e1" : "#64748b"
                }
              },
              shape: {
                borderRadius: 14
              }
            });
          }

Theme provider:


          function AppThemeProvider({ children }: { children: React.ReactNode }) {
            const [mode, setMode] = useState<"light" | "dark">(() => {
              return localStorage.getItem("dashboard-theme") === "dark"
                ? "dark"
                : "light";
            });

            const theme = useMemo(() => {
              return createAppTheme(mode);
            }, [mode]);

            useEffect(() => {
              localStorage.setItem("dashboard-theme", mode);
            }, [mode]);

            return (
              <ThemeProvider theme={theme}>
                <CssBaseline />
                <ThemeModeContext.Provider value={{ mode, setMode }}>
                  {children}
                </ThemeModeContext.Provider>
              </ThemeProvider>
            );
          }

Component using theme tokens:


          <Card
            sx={{
              bgcolor: "background.paper",
              color: "text.primary"
            }}
          >
            <CardContent>
              <Typography color="text.secondary">
                Total sales
              </Typography>
            </CardContent>
          </Card>

Pay attention to: Centralize theme logic. Use MUI theme tokens, persist the selected mode, and avoid scattering dark-mode colors across every component.

6. Treating authentication screens as decoration instead of real workflow states

Many admin dashboard projects include login, signup, forgot password, or profile screens. A common mistake is designing those screens only as static pages without thinking about the workflow. Even in a frontend-only demo, the UI should still model useful states: submitting, invalid credentials, forgotten password, authenticated layout, unauthenticated layout, and redirect after login.

If the project uses Firebase, this becomes even more important because authentication and real-time data can affect what users see. A dashboard should not show admin pages before auth state is known. It should show a loading state while checking the session, then route users either to the dashboard or to the login page. Even for a portfolio project, this makes the app feel more realistic.

Problematic approach:


          function App() {
            return (
              <Routes>
                <Route path="/login" element={<LoginPage />} />
                <Route path="/dashboard" element={<Dashboard />} />
              </Routes>
            );
          }

This allows the dashboard route to render directly. There is no session check, no loading state, and no protected layout.

Better auth state model:


          type AuthState =
            | { status: "checking" }
            | { status: "authenticated"; user: DashboardUser }
            | { status: "unauthenticated" };

          function ProtectedRoute({ authState, children }: {
            authState: AuthState;
            children: React.ReactNode;
          }) {
            if (authState.status === "checking") {
              return <FullPageLoader label="Checking session..." />;
            }

            if (authState.status === "unauthenticated") {
              return <Navigate to="/login" replace />;
            }

            return <>{children}</>;
          }

Protected dashboard route:


          <Routes>
            <Route path="/login" element={<LoginPage />} />

            <Route
              path="/dashboard"
              element={
                <ProtectedRoute authState={authState}>
                  <DashboardLayout />
                </ProtectedRoute>
              }
            />
          </Routes>

Login form state:


          type LoginFormState =
            | { status: "idle" }
            | { status: "submitting" }
            | { status: "error"; message: string };

          <Button
            type="submit"
            disabled={loginState.status === "submitting"}
            variant="contained"
          >
            {loginState.status === "submitting" ? "Signing in..." : "Sign in"}
          </Button>

Pay attention to: Authentication screens should represent real workflow states. Even if the project is mostly UI, model protected routes, loading states, form errors, and redirects clearly.

After completing this project, you will have a strong intermediate React portfolio project that demonstrates real-world component architecture, chart integration, state-driven filtering, responsive layouts, and theme management. This project is especially valuable because analytics dashboards are common in professional frontend work and require more than visual styling. They test your ability to organize data, build reusable components, handle layout complexity, and create an interface that remains useful across different user states, screen sizes, and visual themes.

Reference Implementations Worth Studying

Material UI admin dashboard reference:
behan05 - Material UI Admin Dashboard

This is the closest UI-focused reference for the Dashboard UI Project. It is a modern and responsive admin dashboard built with React, Material UI, and Recharts. The project includes dashboard sections, users, profile, projects, notifications, logs, authentication pages, a customizable AppBar, a responsive sidebar, Redux-controlled layout state, reusable components, light/dark mode, and MUI theme setup.

Pay particular attention to:

  • How Material UI components create a consistent admin interface without building every element from scratch.
  • How the AppBar and Sidebar work together as the main dashboard navigation shell.
  • How Redux Toolkit can manage layout behavior such as sidebar state.
  • How Recharts is used for chart cards and dashboard visualizations.
  • How pages such as users, profile, projects, logs, notifications, login, and signup make the dashboard feel like a complete admin panel.

Use this repository as the main dashboard UI baseline. It is especially helpful for learning how a frontend-only admin panel can be structured with reusable components, routing, theming, and chart sections.

Firebase and real-time dashboard reference:
zoro-diablo - React Firebase Dashboard

This implementation is useful because it adds a backend-connected direction to the dashboard idea. It is a full stack dashboard built with React, Firebase, Sass, Recharts, and Material UI. The project focuses on responsive UI, data visualization, real-time data through Firebase, custom styling with Sass, interactive charts, Material Design, and dark mode.

When studying the code, focus on:

  • How Firebase changes a dashboard from static mock data into a data-driven interface.
  • How real-time database updates can affect cards, charts, and tables.
  • How Sass is used alongside Material UI to customize the visual layer.
  • How Recharts supports interactive dashboard visualizations.
  • What extra safeguards are needed in a production version: environment variables, auth checks, loading states, and error handling.

Use this repository as the data-connected comparison point. It is especially helpful if you want your dashboard project to move beyond static cards and demonstrate dynamic data handling with Firebase.

Full admin-template feature reference:
AnasurRehman - Dashboard

This repository is valuable as a broader admin-template reference. It is a React dashboard frontend built with Material UI, React Router, Recharts, FullCalendar, and related libraries. The project includes light and dark modes, charts and stats, team progress, calendar functionality, contacts, invoices, user profile form, FAQ, bar charts, and line charts.

While reviewing this project, examine:

  • How multiple dashboard pages are organized through routing.
  • How FullCalendar expands the dashboard beyond simple charts and tables.
  • How contacts, invoices, teams, profile, FAQ, and charts create a more complete admin experience.
  • How light and dark screenshots help show that theme support covers the whole interface.
  • How a large dashboard template needs reusable layout, navigation, and page patterns to stay maintainable.

Use this implementation as the full-feature comparison. It helps learners see how a dashboard can grow from one analytics screen into a multi-page admin system with charts, calendar, forms, records, and theme support.

© 2026 ReadyToDev.Pro. All rights reserved.

Methodology

Privacy Policy

Terms & Conditions