DEV Community

Cover image for Building a financial dashboard with HTML5, TailwindCSS v4, and Vanilla JavaScript
John Owolabi Idogun
John Owolabi Idogun

Posted on • Originally published at johnowolabiidogun.dev

Building a financial dashboard with HTML5, TailwindCSS v4, and Vanilla JavaScript

Introduction

While developing the Financial data analyzer series, a dashboard became necessary for presenting the analyzed data intuitively and visually appealingly. Although I'm not a UI/UX designer, I appreciate well-designed interfaces with modern visual cues. This article details my process of creating such a dashboard from scratch with TailwindCSS v4 without using external frameworks.

Prerequisite

This article assumes you're familiar with HTML5, CSS3, and JavaScript (ES syntax). Familiarity with TailwindCSS is also helpful.

I'll present two ways to set up your development environment for working with TailwindCSS from scratch, without external frameworks.

Setup for Tailwind CSS v4 for Node.js users

To get started with Tailwind CSS v4 for your dashboard project using Node.js, follow these steps:

  1. Create your project directory (e.g., finance-dashboard) and navigate into it:

    mkdir finance-dashboard
    cd finance-dashboard
    
  2. Install Tailwind CSS, the Tailwind CSS CLI, and the @tailwindcss/forms plugin as development dependencies:

    npm install -D tailwindcss @tailwindcss/cli @tailwindcss/forms
    
  3. Create an input CSS file (e.g., assets/css/input.css) and add the following:

    @import "tailwindcss";
    @plugin "@tailwindcss/forms";
    
    @custom-variant dark (&:where(.dark, .dark *));
    
    @layer base {
      html {
        scroll-behavior: smooth;
      }
    
      body {
        overflow-y: scroll;
        font-family: "Fira Sans", sans-serif;
      }
    
      input[type],
      textarea,
      select {
        @apply appearance-none border-none ring-0 outline-hidden;
    
        &:focus {
          @apply border-none ring-0 outline-hidden;
        }
    
        &:focus-visible {
          @apply border-none ring-0 outline-hidden;
        }
      }
    
      button {
        @apply cursor-pointer;
      }
    }
    

    This CSS file imports Tailwind CSS, registers the @tailwindcss/forms plugin and sets up a custom dark variant for enabling dark mode via CSS classes. It also includes basic styling for smooth scrolling, font family, and form elements.

    Note: TailwindCSS v4

    We are using strictly TailwindCSS v4 here hence we ditched tailwind.config.[j|t]s file. You can read more in my v3 to v4 migration guide with plugins.

  4. Create your main HTML file (e.g., index.html) with the following structure:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Dashboard | John Owolabi Idogun</title>
        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
          href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Fira+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
          rel="stylesheet"
        />
        <!-- ApexChart -->
        <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
        <!-- Main Stylesheet -->
        <link rel="stylesheet" href="./assets/css/style.css" />
      </head>
      <body class="bg-white text-black dark:bg-gray-900 dark:text-white">
        <!-- body -->
        <script src="./assets/js/app.js"></script>
        <script src="./assets/js/index.charts.js"></script>
      </body>
    </html>
    

    This is a basic HTML structure that includes Google Fonts, ApexCharts (for placeholder charts), and links to your compiled CSS and JavaScript files. The body includes classes for light and dark modes.

  5. Generate the compiled CSS file, assets/css/style.css:

    npx tailwindcss -i ./assets/css/input.css -o ./assets/css/style.css --watch --minify
    

Tailwind CSS v4 Setup without Node.js

For those who prefer not to use Node.js, you can use TailwindCSS's Standalone CLI. Follow the guide to install it based on your operating system. Then, complete steps 1, 3, and 4 from the Node.js setup, skipping steps 2 and 5. To compile your CSS, run:

./tailwindcss -i input.css -o output.css --watch --minify
Enter fullscreen mode Exit fullscreen mode

This setup provides a foundation for building the dashboard with Tailwind CSS v4, utilizing vanilla JavaScript for interactivity and ApexCharts for data visualization.

Source code

GitHub logo Sirneij / finance-dashboard

An aesthetic personal finance dashboard built with vanilla JS, tailwindcss v4 and HTML5

Finance Dashboard

A responsive financial dashboard built with HTML, Tailwind CSS v4, and vanilla JavaScript.

Features

  • Responsive Design: Adapts seamlessly between mobile and desktop views
  • Collapsible Sidebar: Full-width and compact viewing options
  • Dark Mode Support: Automatic system theme detection with manual toggle
  • Real-time Data Visualization: Using ApexCharts for financial data display
  • Mobile-First Approach: Optimized for all screen sizes

Project Structure

finance-dashboard/
'
├── README
├── assets
│   ├── css
│   │   ├── input.css
│   │   └── style.css
│   ├── images
│   │   ├── favicons
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-96x96.png
│   │   │   ├── favicon.ico
│   │   │   ├── favicon.svg
│   │   │   ├── site.webmanifest
│   │   │   ├── web-app-manifest-192x192.png
│   │   │   └── web-app-manifest-512x512.png
│   │   ├── logo-small.svg
│   │   └── logo.svg
│   └── js
│       ├── app.js
│       └── index.charts.js
├── index.html
├── package-lock.json
├── package.json
└── pages
    ├── behavior.html
    └── transactions.html
Enter fullscreen mode Exit fullscreen mode

Getting Started

Implementation

Step 1: Header and Sidebar

First off, we will build out the header and sidebar of the dashboard. Let's add this to the body of the page:

<div
  class="relative h-screen overflow-hidden bg-gray-100 dark:bg-gray-900"
  id="main-content"
>
  <!-- Sidebar -->
  <aside
    id="sidebar"
    class="fixed inset-y-0 left-0 z-30 transform bg-white transition-all duration-300 dark:bg-gray-800"
  >
    <div class="flex h-16 items-center justify-between px-4">
      <a class="flex items-center" href="/">
        <img
          id="logo"
          src="./assets/images/logo.svg"
          alt="Logo"
          class="h-12 w-auto"
        />
      </a>
      <button
        onclick="sidebarController.toggle()"
        class="rounded-sm p-1 hover:bg-gray-100 dark:hover:bg-gray-700"
        aria-label="Toggle sidebar"
      >
        <svg
          class="h-6 w-6 text-gray-600 dark:text-gray-300"
          viewBox="0 0 24 24"
          fill="none"
        >
          <path
            id="toggle-path"
            stroke="currentColor"
            stroke-width="2"
            d="M15 19l-7-7 7-7"
          />
        </svg>
      </button>
    </div>

    <!-- Navigation items -->
    <nav
      id="sidebar-nav"
      class="flex h-[calc(100vh-4rem)] flex-col justify-between px-4"
    >
      <!-- Main Navigation -->
      <div class="space-y-2" id="main-nav">
        <a
          href="index.html"
          data-nav-link
          class="flex items-center rounded-lg px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
          title="Overview"
        >
          <svg
            class="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
            />
          </svg>
          <span class="ml-3" data-nav-label>Overview</span>
        </a>

        <a
          href="behavior.html"
          data-nav-link
          class="flex items-center rounded-lg px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
          title="Behavior Analysis"
        >
          <svg
            class="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
            />
          </svg>
          <span class="ml-3" data-nav-label>Behavior</span>
        </a>

        <a
          href="transactions.html"
          data-nav-link
          class="flex items-center rounded-lg px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
          title="Transaction History"
        >
          <svg
            class="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M4 8h16M4 16h16"
            />
          </svg>
          <span class="ml-3" data-nav-label>Transactions</span>
        </a>
        <a
          href="https://johnowolabiidogun.dev"
          data-nav-link
          class="flex items-center rounded-lg px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
          title="About Developer"
          target="_blank"
        >
          <svg
            class="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M13 10V3L4 14h7v7l9-11h-7z"
            />
          </svg>
          <span class="ml-3" data-nav-label>About Developer</span>
        </a>
      </div>

      <!-- Logout Section -->
      <div class="shrink-0">
        <div class="mb-2 h-px w-full bg-gray-200 dark:bg-gray-700"></div>
        <a
          href="#"
          class="group mb-4 flex items-center rounded-lg px-4 py-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
          title="Logout from application"
        >
          <svg
            class="h-6 w-6"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="2"
              d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
            />
          </svg>
          <span class="ml-3" data-nav-label>Logout</span>
        </a>
      </div>
    </nav>
  </aside>

  <!-- Main Content -->
</div>
Enter fullscreen mode Exit fullscreen mode

The entire page is meant to take the height of the screen (h-screen). This ensures the page remains in view. The sidebar inherits this property. It contains both icons and labels. We'll use both so that when the sidebar is fully open, both are visible, but when it's collapsed, only the icons are displayed.

Next, we will add the main content markup:

<!-- Main Content -->
<div
  class="relative h-full transform transition-all duration-300 md:translate-x-0 md:ml-64"
  id="main"
>
  <header
    class="sticky top-0 z-10 flex h-16 items-center justify-between border-b border-gray-200 bg-white px-6 dark:border-gray-700 dark:bg-gray-800"
  >
    <div class="flex items-center gap-4">
      <!-- Mobile menu button -->
      <button
        class="rounded-lg p-2 text-gray-500 hover:bg-gray-100 md:hidden dark:text-gray-400 dark:hover:bg-gray-700"
        onclick="sidebarController.toggle()"
        aria-label="Toggle Menu"
        title="Toggle Menu"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="h-6 w-6"
        >
          <path stroke="none" d="M0 0h24v24H0z" fill="none" />
          <path d="M4 6h16" />
          <path d="M7 12h13" />
          <path d="M10 18h10" />
        </svg>
      </button>
      <h1 class="text-2xl font-semibold text-gray-800 dark:text-white">
        Dashboard
      </h1>
    </div>
    <button
      id="theme-switcher"
      onclick="themeController.toggle()"
      class="rounded-full bg-white p-2 shadow-lg transition-all duration-300 hover:shadow-xl dark:bg-gray-700 dark:ring-2"
      aria-label="Toggle theme"
    >
      <!-- Sun icon for dark mode -->
      <svg
        id="sun-icon"
        class="h-6 w-6 text-gray-600 dark:text-gray-300 hidden"
        fill="none"
        viewBox="0 0 24 24"
        stroke="currentColor"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
        />
      </svg>
      <!-- Moon icon for light mode -->
      <svg
        id="moon-icon"
        class="h-6 w-6 text-gray-600 dark:text-gray-300"
        fill="none"
        viewBox="0 0 24 24"
        stroke="currentColor"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="2"
          d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
        />
      </svg>
    </button>
  </header>
  <main class="h-[calc(100vh-4rem)] overflow-y-auto p-6">
    <section class="space-y-6">
      <!-- Welcome Section -->
    </section>
  </main>
</div>
Enter fullscreen mode Exit fullscreen mode

At the top, we have the header with the "Dashboard" inscription. It also houses the icons that toggle light/dark modes. The main page takes the remaining height available on the page (h-[calc(100vh-4rem)]) and allows vertical scrolling in case of overflow (overflow-y-auto).

The remaining markups are easy to follow, so we won't paste them here. You can always visit the project's repo to copy them. They look like this for now:

Dark themed dashboard
Light themed dashboard

We'll proceed to write the theme-switching logic and responsive triggers.

Step 2: Responsive triggers and Theme Switching logic

Make your assets/js/app.js look like this:

class SidebarController {
  constructor() {
    this.isSidebarOpen = true;
    this.isMobile = window.innerWidth < 768;
    this.navLinks = document.querySelectorAll("[data-nav-link]");
    this.host = window.location.origin;

    // Cache DOM elements
    this.sidebar = document.getElementById("sidebar");
    this.main = document.getElementById("main");
    this.mobileOverlay = document.getElementById("mobile-overlay");
    this.logo = document.getElementById("logo");
    this.togglePath = document.getElementById("toggle-path");
    this.navLabels = document.querySelectorAll("[data-nav-label]");

    // Bind methods
    this.checkWidth = this.checkWidth.bind(this);
    this.toggle = this.toggle.bind(this);

    // Set initial state
    requestAnimationFrame(() => {
      this.checkWidth();
      this.updateUI(true); // true for initial load
      this.highlightCurrentPage();
    });

    window.addEventListener("resize", this.checkWidth);
  }

  checkWidth() {
    const wasMobile = this.isMobile;
    this.isMobile = window.innerWidth < 768;

    if (wasMobile !== this.isMobile) {
      this.isSidebarOpen = !this.isMobile;
      this.updateUI();
    }
  }

  toggle() {
    this.isSidebarOpen = !this.isSidebarOpen;
    this.updateUI();
  }

  updateUI(isInitialLoad = false) {
    // Mobile specific
    if (this.isMobile) {
      this.sidebar.classList.toggle("-translate-x-full", !this.isSidebarOpen);
      this.main.classList.toggle("overflow-hidden", this.isSidebarOpen);
      this.mobileOverlay?.classList.toggle("hidden", !this.isSidebarOpen);

      // Reset desktop classes
      this.main.classList.remove("md:ml-64", "md:ml-20");
    } else {
      // Desktop specific
      if (isInitialLoad) {
        // Force initial margin on load
        this.main.classList.add("md:ml-64");
      } else {
        this.main.classList.toggle("md:ml-64", this.isSidebarOpen);
        this.main.classList.toggle("md:ml-20", !this.isSidebarOpen);
      }

      // Reset mobile classes
      this.sidebar.classList.remove("-translate-x-full");
      this.main.classList.remove("overflow-hidden");
      this.mobileOverlay?.classList.add("hidden");
    }

    // Common updates
    this.sidebar.classList.toggle("w-64", this.isSidebarOpen);
    this.sidebar.classList.toggle("w-20", !this.isSidebarOpen);

    // Update logo
    if (this.logo) {
      const logoURL = this.host.includes("sirneij.github.io")
        ? `${this.host}/finance-dashboard/assets/images/logo.svg`
        : "./assets/images/logo.svg";
      const logoSmallURL = this.host.includes("sirneij.github.io")
        ? `${this.host}/finance-dashboard/assets/images/logo-small.svg`
        : "./assets/images/logo-small.svg";

      this.logo.src = this.isSidebarOpen
        ? logoURL.replace("null", ".")
        : logoSmallURL.replace("null", ".");

      this.logo.classList.toggle("h-12", this.isSidebarOpen);
      this.logo.classList.toggle("h-8", !this.isSidebarOpen);
    }

    // Update toggle icon
    if (this.togglePath) {
      this.togglePath.setAttribute(
        "d",
        this.isSidebarOpen ? "M15 19l-7-7 7-7" : "M9 19l7-7-7-7"
      );
    }

    // Update labels
    this.navLabels.forEach((label) => {
      label.style.display = this.isSidebarOpen ? "block" : "none";
    });
  }
  highlightCurrentPage() {
    const currentPath = window.location.pathname;

    this.navLinks.forEach((link) => {
      const linkPath = link.getAttribute("href");

      const isActive =
        currentPath === linkPath ||
        (currentPath === "/" && linkPath === "/") ||
        (currentPath !== "/" &&
          linkPath !== "/" &&
          currentPath.includes(linkPath)) ||
        (currentPath === "/" && linkPath === "index.html");

      // Remove existing active classes
      link.classList.remove(
        "bg-gray-100",
        "text-primary-600",
        "dark:bg-gray-700",
        "dark:text-primary-500"
      );

      // Add active classes if current page
      if (isActive) {
        link.classList.add(
          "bg-gray-100",
          "text-primary-600",
          "dark:bg-gray-700",
          "dark:text-primary-500"
        );
      }
    });
  }
}

// Theme handling
const themeController = {
  init() {
    const userTheme = localStorage.getItem("theme");
    const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
    const theme = userTheme || (systemTheme.matches ? "dark" : "light");

    this.updateTheme(theme === "dark");
    systemTheme.addEventListener("change", (e) => {
      if (!localStorage.getItem("theme")) {
        this.updateTheme(e.matches);
      }
    });
  },

  toggle() {
    const isDark = document.documentElement.classList.contains("dark");
    this.updateTheme(!isDark);
    localStorage.setItem("theme", !isDark ? "dark" : "light");
  },

  updateTheme(isDark) {
    document.documentElement.classList.toggle("dark", isDark);
    document.getElementById("sun-icon").classList.toggle("hidden", !isDark);
    document.getElementById("moon-icon").classList.toggle("hidden", isDark);
  },
};

// Initialize when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
  window.sidebarController = new SidebarController();
  themeController.init();
});
Enter fullscreen mode Exit fullscreen mode

This JavaScript code sets up the responsive sidebar and theme-switching logic for the dashboard. SidebarController handles the sidebar's behavior on different screen sizes, including toggling the sidebar and highlighting the current page in the navigation. The ThemeController manages the theme (light/dark) based on user preference or system settings, persisting the choice in local storage. The code uses classes and event listeners for efficient state management and dynamic UI updates.

I opted for classes due to state management issues, especially isSidebarOpen and isMobile. Binding them up here makes it very easy to elegantly manage.

The collapsed versions should look like these:

Dark themed dashboard (collapsed)
Light themed dashboard (collapsed)

Step 3: ApexCharts configurations

For a little taste of the awesome and relatively lightweight charting library (the reason it was preferred compared to Chart.js):

function initializeMonthlyChart() {
  const options = {
    series: [
      {
        name: "Income",
        data: [3000, 3500, 4000, 3800, 4200, 4500],
      },
      {
        name: "Expenses",
        data: [2500, 2800, 3000, 2900, 3100, 3300],
      },
      {
        name: "Savings",
        data: [500, 700, 1000, 900, 1100, 1200],
      },
    ],
    chart: {
      type: "area",
      height: 300,
      toolbar: {
        show: true,
        tools: {
          download: true,
          selection: true,
          zoom: true,
          zoomin: true,
          zoomout: true,
          pan: true,
          reset: true,
        },
        autoSelected: "zoom",
      },
      fontFamily: "inherit",
      background: "transparent",
    },
    colors: ["#22c55e", "#ef4444", "#3b82f6"],
    fill: {
      type: "gradient",
      gradient: {
        shadeIntensity: 1,
        inverseColors: false,
        opacityFrom: 0.5,
        opacityTo: 0,
        stops: [0, 90, 100],
      },
    },
    dataLabels: { enabled: false },
    stroke: {
      width: 2,
      curve: "smooth",
    },
    grid: {
      borderColor: "rgba(156, 163, 175, 0.1)",
      strokeDashArray: 4,
      yaxis: { lines: { show: true } },
      xaxis: { lines: { show: false } },
    },
    xaxis: {
      categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
      labels: {
        style: {
          colors: "rgba(156, 163, 175, 0.9)",
        },
      },
    },
    yaxis: {
      labels: {
        formatter: (value) => `$${value}`,
        style: {
          colors: "rgba(156, 163, 175, 0.9)",
        },
      },
    },
    legend: {
      show: false,
    },
    theme: {
      mode: document.documentElement.classList.contains("dark")
        ? "dark"
        : "light",
    },
  };

  return new ApexCharts(
    document.querySelector("#monthly-summary-chart"),
    options
  );
}

function initializeFinancialChart() {
  const options = {
    series: [
      {
        name: "Income",
        data: [1500, 2000, 1800, 2200, 1900],
      },
      {
        name: "Expenses",
        data: [1200, 1400, 1100, 1600, 1300],
      },
      {
        name: "Balance",
        data: [300, 600, 700, 600, 600],
      },
    ],
    chart: {
      type: "area",
      height: "100%",
      toolbar: {
        show: true,
        tools: {
          download: true,
          selection: true,
          zoom: true,
          zoomin: true,
          zoomout: true,
          pan: true,
          reset: true,
        },
        autoSelected: "zoom",
      },
      fontFamily: "inherit",
      background: "transparent",
    },
    colors: ["#22c55e", "#ef4444", "#3b82f6"],
    fill: {
      type: "gradient",
      gradient: {
        shadeIntensity: 1,
        inverseColors: false,
        opacityFrom: 0.5,
        opacityTo: 0,
        stops: [0, 90, 100],
      },
    },
    dataLabels: { enabled: false },
    stroke: {
      width: 2,
      curve: "smooth",
      dashArray: [0, 0, 5],
    },
    grid: {
      borderColor: "rgba(156, 163, 175, 0.1)",
      strokeDashArray: 4,
      yaxis: { lines: { show: true } },
      xaxis: { lines: { show: false } },
    },
    xaxis: {
      categories: ["Day 1", "Day 2", "Day 3", "Day 4", "Day 5"],
      labels: {
        style: {
          colors: "rgba(156, 163, 175, 0.9)",
        },
      },
    },
    yaxis: {
      labels: {
        formatter: (value) => `$${value}`,
        style: {
          colors: "rgba(156, 163, 175, 0.9)",
        },
      },
    },
    legend: {
      show: false,
    },
    theme: {
      mode: document.documentElement.classList.contains("dark")
        ? "dark"
        : "light",
    },
  };

  return new ApexCharts(
    document.querySelector("#financial-trends-chart"),
    options
  );
}

// Initialize charts when DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
  const monthlyChart = initializeMonthlyChart();
  const financialChart = initializeFinancialChart();

  monthlyChart.render();
  financialChart.render();

  // Handle theme changes
  const observer = new MutationObserver(() => {
    const isDark = document.documentElement.classList.contains("dark");
    monthlyChart.updateOptions({ theme: { mode: isDark ? "dark" : "light" } });
    financialChart.updateOptions({
      theme: { mode: isDark ? "dark" : "light" },
    });
  });

  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ["class"],
  });

  // Handle resize
  window.addEventListener(
    "resize",
    debounce(() => {
      monthlyChart.updateOptions({});
      financialChart.updateOptions({});
    }, 300)
  );
});

function debounce(fn, ms) {
  let timer;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, arguments), ms);
  };
}
Enter fullscreen mode Exit fullscreen mode

This code configures and initializes ApexCharts for the dashboard. initializeMonthlyChart and initializeFinancialChart functions define the options for two different area charts, including series data, chart type, colors, and grid settings. The code also includes a MutationObserver to handle theme changes and a debounce function to optimize resize event handling, ensuring the charts are responsive and adapt to the selected theme.

There are other pages implemented and the repo has them. You can also preview its live version.

Outro

Enjoyed this article? I'm a Software Engineer, Technical Writer, and Technical Support Engineer actively seeking new opportunities, particularly in areas related to web security, finance, healthcare, and education. If you think my expertise aligns with your team's needs, let's chat! You can find me on LinkedIn and X. I am also an email away.

If you found this article valuable, consider sharing it with your network to help spread the knowledge!

Top comments (0)