<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Tailwind CSS - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Tailwind CSS - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 10 May 2026 10:46:56 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/tailwind-css/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Admin Dashboard Sidebar with shadcn/ui and Base UI ]]>
                </title>
                <description>
                    <![CDATA[ Admin dashboards are one of the most common real-world UI components you will build as a React developer. At the heart of nearly every dashboard is a sidebar, a persistent navigation panel that organi ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-admin-dashboard-sidebar-with-shadcn-ui-and-base-ui/</link>
                <guid isPermaLink="false">69de6a6491716f3cfb542305</guid>
                
                    <category>
                        <![CDATA[ shadcn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ baseui ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vaibhav Gupta ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 16:25:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3ce152b1-9a34-4c72-85f0-cabf7d4f3460.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Admin dashboards are one of the most common real-world UI components you will build as a React developer. At the heart of nearly every dashboard is a sidebar, a persistent navigation panel that organizes pages, tools, and features into a clean, scannable structure.</p>
<p>Building a sidebar from scratch involves much more than an <code>&lt;nav&gt;</code> element. You need collapsible submenus, active state tracking across parent and child items, accessible keyboard navigation, a scroll area for long nav lists, and a consistent design system that holds together across screen sizes.</p>
<p>In this tutorial, you'll learn how to build a fully functional, accessible admin dashboard sidebar using shadcn/ui, a collection of beautifully designed, accessible React components, and a pre-built community block from Shadcn Space, which extends shadcn/ui with ready-to-use dashboard UI patterns.</p>
<p>By the end of this tutorial, you'll have a working sidebar that includes:</p>
<ul>
<li><p>Grouped navigation sections with uppercase labels</p>
</li>
<li><p>Collapsible parent menu items with child links</p>
</li>
<li><p>Active state tracking across both parent and child items</p>
</li>
<li><p>A floating sidebar with an independent scroll area</p>
</li>
<li><p>A promotional card pinned inside the sidebar footer</p>
</li>
</ul>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-you-will-build">What You Will Build</a></p>
</li>
<li><p><a href="#heading-why-shadcnui">Why shadcn/ui?</a></p>
</li>
<li><p><a href="#heading-what-is-shadcn-space">What is Shadcn Space?</a></p>
</li>
<li><p><a href="#heading-how-to-install-the-sidebar-block">How to Install the Sidebar Block</a></p>
</li>
<li><p><a href="#heading-how-to-understand-the-folder-structure">How to Understand the Folder Structure</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-page-layout">How to Build the Page Layout</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-appsidebar-component">How to Build the AppSidebar Component</a></p>
</li>
<li><p><a href="#heading-how-to-define-the-navigation-data">How to Define the Navigation Data</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-navmain-component">How to Build the NavMain Component</a></p>
</li>
<li><p><a href="#heading-how-to-handle-active-states-and-collapsible-menus">How to Handle Active States and Collapsible Menus</a></p>
</li>
<li><p><a href="#heading-how-to-style-the-sidebar">How to Style the Sidebar</a></p>
</li>
<li><p><a href="#heading-live-preview">Live Preview</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before you start, make sure you have the following:</p>
<ul>
<li><p>Node.js 18+ installed on your machine</p>
</li>
<li><p>Basic knowledge of React and TypeScript</p>
</li>
<li><p>Familiarity with Tailwind CSS utility classes</p>
</li>
<li><p>A package manager installed (npm, pnpm, yarn, or bun)</p>
</li>
</ul>
<p>You don't need prior experience with shadcn/ui, but it helps to have read through <a href="https://ui.shadcn.com/docs">the official docs</a> at least once.</p>
<h2 id="heading-what-you-will-build"><strong>What You Will Build</strong></h2>
<p>In this article, you'll build a fully functional admin dashboard sidebar with the following features:</p>
<ol>
<li><p><strong>Floating sidebar shell</strong>: a card-style sidebar with rounded corners, a drop shadow, and a configurable width</p>
</li>
<li><p><strong>Grouped navigation</strong>: navigation items organized under section labels like Dashboards, Pages, Apps, and Form Elements</p>
</li>
<li><p><strong>Collapsible submenus</strong>: parent items like Blogs and Shadcn Forms that expand on click to reveal child links</p>
</li>
<li><p><strong>Active state tracking</strong>: visual highlighting of the selected parent and child item at all times</p>
</li>
<li><p><strong>Sidebar toggle</strong>: a trigger button in the page header that opens and closes the sidebar</p>
</li>
<li><p><strong>Promotional card</strong>: a "Get Premium" card at the bottom of the sidebar scroll area</p>
</li>
</ol>
<h2 id="heading-why-shadcnui"><strong>Why shadcn/ui?</strong></h2>
<p><a href="https://ui.shadcn.com/"><strong>shadcn/ui</strong></a> is a collection of beautifully designed, accessible React components built on top of Radix UI and styled with Tailwind CSS.</p>
<p>Instead of installing a traditional component library as a dependency, you copy components directly into your project using a CLI. This gives you full ownership of the code structure and styling. You can read every line, change anything, and the components never break because of a library update you didn't control.</p>
<p>Some key benefits of shadcn/ui include:</p>
<ul>
<li><p>Accessible by default, built on Radix and Base UI primitives</p>
</li>
<li><p>Fully styled with Tailwind CSS utility classes</p>
</li>
<li><p>Zero lock-in: the code lives in your project, not inside <code>node_modules</code></p>
</li>
<li><p>Works with Next.js, React, Astro, Vite, and other frameworks</p>
</li>
<li><p>A growing ecosystem of community-built blocks and registries</p>
</li>
</ul>
<p>The <code>Sidebar, Collapsible, ScrollArea, Card, and Button</code> Components you'll use in this tutorial all come from shadcn/ui.</p>
<h2 id="heading-what-is-shadcn-space"><strong>What is Shadcn Space?</strong></h2>
<p><strong>Shadcn Space</strong> is an open-source library of pre-built <a href="https://shadcnspace.com/blocks"><strong>UI blocks</strong></a> built on top of shadcn/ui. It provides ready-to-use dashboard layouts, sidebars, tables, cards, and other common admin UI patterns so you don't have to assemble them from individual primitives every time.</p>
<p>Each block in Shadcn Space is installable directly into your project using the shadcn CLI. Once installed, the code is yours: you can read it, extend it, and adapt it to your design system without any runtime dependency on Shadcn Space itself.</p>
<p>For this tutorial, you'll use the <code>sidebar-06</code> block (it’s free to use), which is a floating admin sidebar with grouped navigation, collapsible submenus, and an integrated scroll area.</p>
<p>Shadcn Space also provides a companion <a href="https://www.figma.com/community/file/1597967874273587400/shadcn-space-figma-ui-kit"><strong>Figma UI Kit</strong></a> that matches the design system used in the blocks, which is useful if you do design work alongside development.</p>
<p>You can explore the full block library and the getting-started documentation in the <a href="https://shadcnspace.com/docs/getting-started/blocks"><strong>official Shadcn Space docs</strong></a>.</p>
<h3 id="heading-how-to-set-up-the-project">How to Set Up the Project</h3>
<p>Start by creating a new Next.js project if you don't already have one:</p>
<pre><code class="language-javascript">npx shadcn@latest init --preset b0 --base base --template next
</code></pre>
<p>This command:</p>
<ul>
<li><p>Creates a Next.js project</p>
</li>
<li><p>Configures Tailwind CSS</p>
</li>
<li><p>Sets up Base UI as the component foundation</p>
</li>
<li><p>Uses Nova style preset</p>
</li>
<li><p>Configures Lucide icons</p>
</li>
<li><p>Uses Inter font</p>
</li>
<li><p>Applies neutral theme tokens</p>
</li>
</ul>
<p>Follow the prompts to configure your base color, CSS variables, and component output directory. This sets up the <code>components/ui</code> directory and the required Tailwind configuration that all shadcn/ui components depend on.</p>
<p>Once the initialization is complete, your <code>components.json</code> project will be created at the root of your project. This file tells the shadcn CLI where to place components, what path aliases you're using, and which styling configuration to follow.</p>
<p>Add this in <code>components.json</code>:</p>
<pre><code class="language-javascript">{
  "registries": {
    "@shadcn-space": {
      "url": "https://shadcnspace.com/r/{name}.json",
    }
  }
}
</code></pre>
<h2 id="heading-how-to-install-the-sidebar-block"><strong>How to Install the Sidebar Block</strong></h2>
<p>With shadcn/ui initialized, you can now pull in the <code>sidebar-06</code> block from Shadcn Space. While Shadcn Space provides components for both Radix UI and Base UI, this tutorial uses the Base UI version. Run one of the following commands depending on your package manager:</p>
<p><strong>npm:</strong></p>
<pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>pnpm:</strong></p>
<pre><code class="language-javascript">pnpm dlx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>yarn:</strong></p>
<pre><code class="language-javascript">yarn dlx shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p><strong>bun:</strong></p>
<pre><code class="language-javascript">bunx --bun shadcn@latest add @shadcn-space/sidebar-06
</code></pre>
<p>This command fetches the block from the Shadcn Space registry and scaffolds all the required component files into your project automatically. It also installs any shadcn/ui primitives the block depends on (such as <code>Sidebar, ScrollArea, Card, Button,</code> and <code>Collapsible</code>) if they aren't already present in your components/ui directory.</p>
<p>You can preview the live block and find the installation command on their <a href="https://shadcnspace.com/blocks/dashboard-ui/sidebars"><strong>shadcn sidebar</strong></a> page.</p>
<h2 id="heading-how-to-understand-the-folder-structure"><strong>How to Understand the Folder Structure</strong></h2>
<p>After installation, your project will contain the following new files:</p>
<pre><code class="language-javascript">app/
  sidebar-06/
    page.tsx                  ← Route entry point
assets/
  logo/
    logo.tsx                  ← Logo component
components/
  shadcn-space/
    blocks/
      sidebar-06/
        app-sidebar.tsx       ← Main sidebar shell
        nav-main.tsx          ← Navigation logic and rendering
</code></pre>
<p>Each file has a clearly defined responsibility:</p>
<ul>
<li><p><code>app/sidebar-06/page.tsx</code>: the route entry point that wires the sidebar into a page layout using SidebarProvider</p>
</li>
<li><p><code>assets/logo/logo.tsx</code>: the logo component rendered in the sidebar header</p>
</li>
<li><p><code>components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx</code>: the main sidebar shell, including the header, scroll area, nav data, and promotional card</p>
</li>
<li><p><code>components/shadcn-space/blocks/sidebar-06/nav-main.tsx</code>: all navigation rendering logic, including section labels, leaf items, collapsible parents, and active state management</p>
</li>
</ul>
<p>You'll work through each of these files in detail in the sections below.</p>
<h2 id="heading-how-to-build-the-page-layout"><strong>How to Build the Page Layout</strong></h2>
<p>Open <code>app/sidebar-06/page.tsx</code>. This file is the entry point for your dashboard page. It uses <code>SidebarProvider</code> to establish sidebar context across the page, and <code>SidebarTrigger</code> to render a toggle button inside the header.</p>
<pre><code class="language-javascript">import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/shadcn-space/blocks/sidebar-06/app-sidebar";

const Page = () =&gt; {
  return (
    &lt;SidebarProvider
      className="p-4 bg-muted"
      style={{ "--sidebar-width": "300px" } as React.CSSProperties}
    &gt;
      &lt;AppSidebar /&gt;

      {/* Main content area */}
      &lt;div className="flex flex-1 flex-col gap-4"&gt;
        &lt;header className="flex h-14 shrink-0 items-center gap-2 rounded-xl bg-background px-4 shadow-sm"&gt;
          &lt;SidebarTrigger className="cursor-pointer" /&gt;
        &lt;/header&gt;
        &lt;main className="flex-1 rounded-xl bg-background" /&gt;
      &lt;/div&gt;
    &lt;/SidebarProvider&gt;
  );
};

export default Page;
</code></pre>
<p>Let's break down the key parts of this layout:</p>
<p><strong>SidebarProvider</strong> wraps everything on the page. It manages the sidebar's open/closed state and passes it down to child components via React context. Any component that needs to read or change the sidebar state, including <code>SidebarTrigger</code> and <code>AppSidebar</code>, must be a descendant of <code>SidebarProvider</code>.</p>
<p><strong>The</strong> <code>--sidebar-width</code> <strong>CSS custom property</strong> controls the rendered width of the sidebar. It's set inline using a type assertion (<code>as React.CSSProperties</code>) because TypeScript doesn't know about this custom property by default. Setting it here rather than in a CSS file keeps the width configurable on a per-page basis.</p>
<p><code>SidebarTrigger</code> is a toggle button component that reads the sidebar open/closed state from the nearest <code>SidebarProvider</code> context and flips it on click. It renders in the header so users always have access to the toggle regardless of scroll position.</p>
<p><code>bg-muted</code> on <code>SidebarProvider</code> creates the light gray outer background that makes the floating sidebar card visually stand out from the page.</p>
<h2 id="heading-how-to-build-the-appsidebar-component"><strong>How to Build the AppSidebar Component</strong></h2>
<p>Open <code>components/shadcn-space/blocks/sidebar-06/app-sidebar.tsx</code>. This component is the main sidebar shell. It composes shadcn/ui's <code>Sidebar</code>, <code>SidebarHeader</code>, and <code>SidebarContent</code> layout primitives and wraps the scrollable navigation area in a <code>ScrollArea</code> component to handle overflow independently.</p>
<pre><code class="language-javascript">"use client";

import {
  Sidebar,
  SidebarContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import Logo from "@/assets/logo/logo";
import { NavItem, NavMain } from "@/components/shadcn-space/blocks/sidebar-06/nav-main";
import {
  AlignStartVertical,
  PieChart,
  CircleUserRound,
  ClipboardList,
  Notebook,
  NotepadText,
  Table,
  Languages,
  Ticket,
} from "lucide-react";
</code></pre>
<p>The <code>"use client"</code> directive at the top is required because this component uses React state (through <code>NavMain</code>) and event handlers, both of which require the component to run in the browser rather than being server-rendered by Next.js.</p>
<h2 id="heading-how-to-define-the-navigation-data"><strong>How to Define the Navigation Data</strong></h2>
<p>Inside <code>app-sidebar.tsx</code>the navigation structure is defined as a flat array of <code>NavItem</code> objects. Each item belongs to one of three categories:</p>
<ol>
<li><p><strong>A section label</strong> marked with <code>isSection: true</code> and a <code>label</code> string. Renders as an uppercase group heading.</p>
</li>
<li><p><strong>A leaf item</strong> has a <code>title, icon</code>, and <code>href</code>, but no <code>children</code>. Renders as a direct navigation link.</p>
</li>
<li><p><strong>A parent item</strong> has a <code>title, icon</code>, and a <code>children</code> array of sub-items. Renders as a collapsible trigger.</p>
</li>
</ol>
<pre><code class="language-javascript">
export const navData: NavItem[] = [
  // Dashboards Section
  { label: "Dashboards", isSection: true },
  { title: "Analytics", icon: PieChart, href: "#" },
  { title: "CRM Dashboard", icon: ClipboardList, href: "#" },

  // Pages Section
  { label: "Pages", isSection: true },
  { title: "Tables", icon: Table, href: "#" },
  { title: "Forms", icon: ClipboardList, href: "#" },
  { title: "User Profile", icon: CircleUserRound, href: "#" },

  // Apps Section
  { label: "Apps", isSection: true },
  { title: "Notes", icon: Notebook, href: "#" },
  { title: "Tickets", icon: Ticket, href: "#" },
  {
    title: "Blogs",
    icon: Languages,
    children: [
      { title: "Blog Post", href: "#" },
      { title: "Blog Detail", href: "#" },
      { title: "Blog Edit", href: "#" },
      { title: "Blog Create", href: "#" },
      { title: "Manage Blogs", href: "#" },
    ],
  },

  // Form Elements Section
  { label: "Form Elements", isSection: true },
  {
    title: "Shadcn Forms",
    icon: NotepadText,
    children: [
      { title: "Button", href: "#" },
      { title: "Input", href: "#" },
      { title: "Select", href: "#" },
      { title: "Checkbox", href: "#" },
      { title: "Radio", href: "#" },
    ],
  },
  {
    title: "Form layouts",
    icon: AlignStartVertical,
    children: [
      { title: "Forms Horizontal", href: "#" },
      { title: "Forms Vertical", href: "#" },
      { title: "Forms Validation", href: "#" },
      { title: "Forms Examples", href: "#" },
      { title: "Forms Wizard", href: "#" },
    ],
  },
];
</code></pre>
<p>This flat array approach is intentionally simple to maintain. You don't need a nested tree structure because the <code>NavMain</code> component handles the rendering logic for each item type by inspecting each item's shape. Adding a new section, item, or submenu is as straightforward as appending a new object to the array.</p>
<h2 id="heading-how-to-build-the-navmain-component"><strong>How to Build the NavMain Component</strong></h2>
<p>Open <code>components/shadcn-space/blocks/sidebar-06/nav-main.tsx</code>. This file contains all the navigation rendering logic. Start with the type definition and the top-level <code>NavMain</code> function:</p>
<pre><code class="language-javascript">"use client";

import * as React from "react";
import { ChevronRight, LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
} from "@/components/ui/collapsible";
import {
  SidebarGroup,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubItem,
  SidebarMenuSubButton,
} from "@/components/ui/sidebar";

export type NavItem = {
  label?: string;
  isSection?: boolean;
  title?: string;
  icon?: LucideIcon;
  href?: string;
  children?: NavItem[];
};

export function NavMain({ items }: { items: NavItem[] }) {
  const [activeParent, setActiveParent] = React.useState&lt;string | null&gt;(
    items.find((i) =&gt; !i.isSection)?.title || null
  );
  const [activeChild, setActiveChild] = React.useState&lt;string | null&gt;(null);

  return (
    &lt;&gt;
      {items.map((item, index) =&gt; (
        &lt;NavMainItem
          key={item.title || item.label || index}
          item={item}
          activeParent={activeParent}
          setActiveParent={setActiveParent}
          activeChild={activeChild}
          setActiveChild={setActiveChild}
        /&gt;
      ))}
    &lt;/&gt;
  );
}
</code></pre>
<p><code>activeParent</code> tracks which top-level nav item is currently selected. It initializes to the title of the first non-section item, so the sidebar always has a selection on first render, and you never show the sidebar with nothing highlighted. <code>activeChild</code> tracks which sub-item inside a collapsible menu is selected.</p>
<p>Both state values are passed down as props to each <code>NavMainItem</code>, so every item in the list can read the current selection and trigger updates to it.</p>
<h2 id="heading-how-to-handle-active-states-and-collapsible-menus"><strong>How to Handle Active States and Collapsible Menus</strong></h2>
<p>The <code>NavMainItem</code> function branches into one of three rendering paths based on the shape of the incoming item.</p>
<h3 id="heading-how-to-render-section-labels">How to Render Section Labels</h3>
<pre><code class="language-javascript">if (item.isSection &amp;&amp; item.label) {
  return (
    &lt;SidebarGroup className="p-0 pt-5 first:pt-0"&gt;
      &lt;SidebarGroupLabel className="p-0 text-xs font-medium uppercase text-sidebar-foreground"&gt;
        {item.label}
      &lt;/SidebarGroupLabel&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>Section labels use <code>first:pt-0</code> to remove the top padding from the very first section, so the nav starts flush with the header.</p>
<h3 id="heading-how-to-render-collapsible-parent-items">How to Render Collapsible Parent Items</h3>
<pre><code class="language-javascript">if (hasChildren &amp;&amp; item.title) {
  return (
    &lt;SidebarGroup className="p-0"&gt;
      &lt;SidebarMenu&gt;
        &lt;Collapsible open={isOpen} onOpenChange={setIsOpen}&gt;
          &lt;SidebarMenuItem&gt;
            &lt;CollapsibleTrigger
              className="w-full"
              render={
                &lt;SidebarMenuButton
                  id={`nav-main-trigger-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
                  tooltip={item.title}
                  isActive={isParentActive}
                  onClick={() =&gt; setActiveParent(item.title!)}
                  className={cn(
                    "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
                    isParentActive ? "bg-primary! text-primary-foreground!" : ""
                  )}
                &gt;
                  {item.icon &amp;&amp; &lt;item.icon size={16} /&gt;}
                  &lt;span&gt;{item.title}&lt;/span&gt;
                  &lt;ChevronRight
                    className={cn(
                      "ml-auto transition-transform duration-200",
                      isOpen &amp;&amp; "rotate-90"
                    )}
                  /&gt;
                &lt;/SidebarMenuButton&gt;
              }
            /&gt;
            &lt;CollapsibleContent&gt;
              &lt;SidebarMenuSub className="me-0 pe-0"&gt;
                {item.children!.map((child, index) =&gt; (
                  &lt;NavMainSubItem
                    key={child.title || index}
                    item={child}
                    activeParent={activeParent}
                    setActiveParent={setActiveParent}
                    activeChild={activeChild}
                    setActiveChild={setActiveChild}
                    parentTitle={item.title}
                  /&gt;
                ))}
              &lt;/SidebarMenuSub&gt;
            &lt;/CollapsibleContent&gt;
          &lt;/SidebarMenuItem&gt;
        &lt;/Collapsible&gt;
      &lt;/SidebarMenu&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>A <code>useEffect</code> inside <code>NavMainItem</code> syncs the local <code>isOpen</code> state with <code>activeParent</code> so that when a different parent is activated, the previously open collapsible stays open until the user explicitly closes it:</p>
<pre><code class="language-javascript">React.useEffect(() =&gt; {
  if (isParentActive) {
    setIsOpen(true);
  }
}, [isParentActive]);
</code></pre>
<p>The <code>ChevronRight</code> icon rotates 90 degrees when the submenu is open, using a Tailwind transition class:</p>
<pre><code class="language-javascript">&lt;ChevronRight
  className={cn(
    "ml-auto transition-transform duration-200",
    isOpen &amp;&amp; "rotate-90"
  )}
/&gt;
</code></pre>
<h3 id="heading-how-to-render-leaf-items">How to Render Leaf Items</h3>
<pre><code class="language-javascript">if (item.title) {
  return (
    &lt;SidebarGroup className="p-0"&gt;
      &lt;SidebarMenu&gt;
        &lt;SidebarMenuItem&gt;
          &lt;SidebarMenuButton
            id={`nav-main-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
            tooltip={item.title}
            isActive={isParentActive}
            onClick={() =&gt; {
              setActiveParent(item.title!);
              setActiveChild(null);
            }}
            className={cn(
              "rounded-md text-sm font-medium px-3 py-2 h-9 transition-colors cursor-pointer",
              isParentActive ? "bg-primary! text-primary-foreground!" : ""
            )}
            render={&lt;a href={item.href} /&gt;}
          &gt;
            {item.icon &amp;&amp; &lt;item.icon /&gt;}
            {item.title}
          &lt;/SidebarMenuButton&gt;
        &lt;/SidebarMenuItem&gt;
      &lt;/SidebarMenu&gt;
    &lt;/SidebarGroup&gt;
  );
}
</code></pre>
<p>The <code>render</code> prop on <code>SidebarMenuButton</code> replaces the default button element with an <code>&lt;a&gt;</code> tag. This preserves correct anchor link semantics and accessibility while keeping the button's visual styling. When a leaf item is clicked, <code>activeChild</code> is reset to <code>null</code> since there is no child to track.</p>
<h3 id="heading-how-to-render-child-items-in-a-submenu">How to Render Child Items in a Submenu</h3>
<p>The <code>NavMainSubItem</code> function handles sub-items inside a collapsible. When a child is clicked, it sets both <code>activeChild</code> to itself and <code>activeParent</code> to its parent's title so the parent item remains visually highlighted:</p>
<pre><code class="language-javascript">if (item.title) {
  return (
    &lt;SidebarMenuSubItem className="w-full"&gt;
      &lt;SidebarMenuSubButton
        id={`nav-sub-button-${item.title.toLowerCase().replace(/\s+/g, '-')}`}
        className={cn(
          "w-full rounded-md transition-colors",
          activeChild === item.title ? "bg-muted! text-foreground!" : ""
        )}
        isActive={activeChild === item.title}
        onClick={() =&gt; {
          setActiveParent(parentTitle || "");
          setActiveChild(item.title!);
        }}
        render={&lt;a href={item.href}&gt;{item.title}&lt;/a&gt;}
      /&gt;
    &lt;/SidebarMenuSubItem&gt;
  );
}
</code></pre>
<p>The child uses a different active style (<code>bg-muted</code> with <code>text-foreground</code>) compared to the parent (<code>bg-primary</code> with <code>text-primary-foreground</code>). This visual distinction makes it easy to see both which section you are in and which specific page is currently active.</p>
<p>Sub-items also support nesting. If a child item itself has a <code>children</code> array, <code>NavMainSubItem</code> renders another <code>Collapsible</code> with a nested <code>SidebarMenuSub</code>, allowing you to build multi-level navigation trees without any changes to the data structure.</p>
<h2 id="heading-how-to-style-the-sidebar"><strong>How to Style the Sidebar</strong></h2>
<p>The full <code>AppSidebar</code> render function puts all of the pieces together:</p>
<pre><code class="language-javascript">export function AppSidebar() {
  return (
    &lt;Sidebar variant="floating" className="p-4 h-full [&amp;_[data-slot=sidebar-inner]]:h-full"&gt;
      &lt;div className="flex flex-col gap-6 overflow-hidden"&gt;

        {/* Header with Logo */}
        &lt;SidebarHeader className="px-4"&gt;
          &lt;SidebarMenu&gt;
            &lt;SidebarMenuItem&gt;
              &lt;a href="#" className="w-full h-full"&gt;
                &lt;Logo /&gt;
              &lt;/a&gt;
            &lt;/SidebarMenuItem&gt;
          &lt;/SidebarMenu&gt;
        &lt;/SidebarHeader&gt;

        {/* Scrollable Navigation Content */}
        &lt;SidebarContent className="overflow-hidden"&gt;
          &lt;ScrollArea className="h-[calc(100vh-100px)]"&gt;
            &lt;div className="px-4"&gt;
              &lt;NavMain items={navData} /&gt;
            &lt;/div&gt;

            {/* Promotional Card */}
            &lt;div className="pt-5 px-4"&gt;
              &lt;Card className="shadow-none ring-0 bg-secondary px-4 py-6"&gt;
                &lt;CardContent className="p-0 flex flex-col gap-3 items-center"&gt;
                  &lt;img
                    src="https://images.shadcnspace.com/assets/backgrounds/download-img.png"
                    alt="sidebar-img"
                    width={74}
                    height={74}
                    className="h-20 w-20"
                  /&gt;
                  &lt;div className="flex flex-col gap-4 items-center"&gt;
                    &lt;div&gt;
                      &lt;p className="text-base font-semibold text-card-foreground text-center"&gt;
                        Grab Pro Now
                      &lt;/p&gt;
                      &lt;p className="text-sm font-regular text-muted-foreground text-center"&gt;
                        Customize your admin
                      &lt;/p&gt;
                    &lt;/div&gt;
                    &lt;Button className="w-fit h-9 px-4 py-2 shadow-none cursor-pointer rounded-xl hover:bg-primary/80"&gt;
                      Get Premium
                    &lt;/Button&gt;
                  &lt;/div&gt;
                &lt;/CardContent&gt;
              &lt;/Card&gt;
            &lt;/div&gt;
          &lt;/ScrollArea&gt;
        &lt;/SidebarContent&gt;

      &lt;/div&gt;
    &lt;/Sidebar&gt;
  );
}
</code></pre>
<p>Let's walk through the key styling decisions:</p>
<p><code>variant="floating"</code> gives the sidebar a card-like appearance with rounded corners and a subtle drop shadow. It visually lifts the sidebar off the background rather than making it flush with the page edge like a standard sidebar would.</p>
<p><code>[&amp;_[data-slot=sidebar-inner]]:h-full</code> is an arbitrary Tailwind variant selector that targets shadcn/ui's internal sidebar slot element. Without this, the sidebar inner container doesn't fill the full available height, which breaks the layout. The <code>data-slot</code> attribute is how shadcn/ui identifies internal sub-elements of compound components.</p>
<p><code>h-[calc(100vh-100px)]</code> on <code>ScrollArea</code> makes the navigation list independently scrollable. The 100px offset accounts for the sidebar header and padding, so the scroll area doesn't overflow the viewport. The rest of the page layout remains static while the nav scrolls.</p>
<p>The <code>bg-secondary</code> card at the bottom of the scroll area is a common admin dashboard pattern, a soft prompt for an upgrade or onboarding action that lives passively in the sidebar without blocking navigation.</p>
<p>For more details on the Sidebar component's API, variants, and configuration options, refer to the official shadcn/ui sidebar docs.</p>
<h2 id="heading-live-preview"><strong>Live Preview</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/68b53a3d851476bd2ce87f12/f1538441-fa73-4eb0-af91-04f5bf4fab08.png" alt="f1538441-fa73-4eb0-af91-04f5bf4fab08" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>Congratulations! You have now built a complete, production-ready admin dashboard sidebar using shadcn/ui and a community block from Shadcn Space.</p>
<p>Here is a recap of everything you covered:</p>
<ul>
<li><p>Setting up a Next.js project with shadcn/ui initialized and a pre-built sidebar block installed from Shadcn Space</p>
</li>
<li><p>Using <code>SidebarProvider</code> and <code>SidebarTrigger</code> to manage the sidebar open/closed state across a page layout through React context</p>
</li>
<li><p>Defining navigation data as a flat array of typed <code>NavItem</code> objects covering section labels, leaf items, and collapsible parent items</p>
</li>
<li><p>Rendering all three item types from a single <code>navData</code> source in the <code>NavMain</code> and <code>NavMainItem</code> components</p>
</li>
<li><p>Tracking <code>activeParent</code> and <code>activeChild</code> state in a single location and passing them as props so every item can read and update the shared selection state</p>
</li>
<li><p>Using <code>Collapsible</code> with a <code>useEffect</code> sync to keep parent items open when they are active, and animate the chevron icon on expand and collapse</p>
</li>
<li><p>Applying the <code>floating</code> variant, an arbitrary Tailwind slot selector, and <code>ScrollArea</code> with a calculated height to produce a polished, production-appropriate sidebar layout</p>
</li>
</ul>
<p>This pattern scales well beyond what you built here. You can extend <code>NavItem</code> with additional fields like badge counts, permission flags, or external link indicators. You can swap in real <code>href</code> values and connect <code>activeParent</code> and <code>activeChild</code> to your router so the selection always reflects the current URL. You can also add more sections to <code>navData</code> without touching any rendering logic.</p>
<p>For a quick checkout, we have used the Shadcn Space free Shadcn dashboard block in this <a href="https://shadcnspace.com/blocks/dashboard-ui/dashboard-shell"><strong>dashboard shell</strong></a>.</p>
<p>If you want to explore more pre-built admin UI blocks, components, and templates built on top of shadcn/ui, you can browse the full library at <a href="https://shadcnspace.com/"><strong>Shadcn Space</strong></a>.</p>
<h3 id="heading-resources"><strong>Resources</strong></h3>
<ul>
<li><p><a href="https://shadcnspace.com/blocks"><strong>Shadcn UI Blocks</strong></a></p>
</li>
<li><p><a href="https://shadcnspace.com/components"><strong>Shadcn UI Components</strong></a></p>
</li>
<li><p><a href="https://shadcnspace.com/docs/getting-started/blocks"><strong>Shadcn Space Getting Started Docs</strong></a></p>
</li>
<li><p><a href="https://www.figma.com/community/file/1597967874273587400/shadcn-space-figma-ui-kit"><strong>Figma UI Kit Design System</strong></a></p>
</li>
<li><p><a href="https://ui.shadcn.com/docs/components/sidebar"><strong>shadcn/ui Sidebar Docs</strong></a></p>
</li>
<li><p><a href="https://base-ui.com/"><strong>Base UI</strong></a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Atomic CSS and Functional Programming Are Related ]]>
                </title>
                <description>
                    <![CDATA[ Hello, friends! My name is Ramazan, and I'm a front-end developer and enthusiast who loves looking at familiar things in web development from new perspectives. You might have heard of functional progr ]]>
                </description>
                <link>https://www.freecodecamp.org/news/atomic-and-functional-css/</link>
                <guid isPermaLink="false">69bc348fb238fd45a320a2f7</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ layout ]]>
                    </category>
                
                    <category>
                        <![CDATA[ atomic css ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mlut ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ramazan Maksyutov ]]>
                </dc:creator>
                <pubDate>Thu, 19 Mar 2026 17:38:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/64d0d72a-075e-42a9-b0fb-ca577f950999.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hello, friends!</p>
<p>My name is Ramazan, and I'm a front-end developer and enthusiast who loves looking at familiar things in web development from new perspectives.</p>
<p>You might have heard of functional programming (FP). It's a paradigm characterised by the use of pure functions and the preservation of data immutability. There are even languages in which FP principles prevail, like Haskell, OCaml, and Elixir. Other languages like JavaScript, Python, and C++ also support this approach, although they're not limited to it.</p>
<p>But an attentive reader will look at the title and ask: "Functional programming is fine. But what does Atomic CSS have to do with it?" I'll answer that now!</p>
<p>The thing is, back when the atomic approach first appeared, it had another name: Functional CSS. Some people still use it quite often today to avoid confusion with other terms with the same <a href="https://css-tricks.com/the-atomics/">name</a>. But why is this approach to writing CSS called functional?</p>
<p>That's the question I'll try to answer in this article. First, I'll describe the basic principles of FP. Then, I'll talk about the basics of Atomic CSS (which I'll refer to here as ACSS), drawing analogies with functional programming. I'll also try to use simple examples to show what problems you can solve by applying Atomic CSS to styling.</p>
<p>When preparing the materials for this article, I relied a lot on <a href="https://www.youtube.com/watch?v=7g0BHu0kWXo">this tutorial</a> on FP and <a href="https://www.youtube.com/watch?v=uHVqbCPnOwU">this one</a> on Functional CSS.</p>
<p>Well, let's get started!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this article, all you need is a basic understanding of HTML, CSS, and JavaScript. There are also some examples where we'll use the Atomic CSS framework <a href="https://mlut.style/">mlut</a>, but you don't have to know its syntax because I've provided the text with the equivalent CSS styles.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ol>
<li><p><a href="#heading-the-basic-principles-of-functional-programming">The Basic Principles of Functional Programming</a></p>
<ul>
<li><p><a href="#heading-pure-functions">Pure Functions</a></p>
</li>
<li><p><a href="#heading-immutability">Immutability</a></p>
</li>
<li><p><a href="#heading-function-composition">Function Composition</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-the-principles-of-fp-meet-their-incarnation-in-atomic-css">How the Principles of FP Meet Their Incarnation in Atomic CSS</a></p>
<ul>
<li><p><a href="#heading-purity">Purity</a></p>
</li>
<li><p><a href="#heading-immutability-in-acss">Immutability in ACSS</a></p>
</li>
<li><p><a href="#heading-composition">Composition</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-the-basic-principles-of-functional-programming">The Basic Principles of Functional Programming</h2>
<p>Functional programming is a broad field that has been the subject of numerous complex articles and an entire scientific <a href="https://www.cambridge.org/core/journals/journal-of-functional-programming">journal</a>. So in my article, I'll focus on exploring only the basic principles of FP and drawing analogies with them in Atomic CSS.</p>
<p>This approach is based on the idea that all the actions we need to perform in a program should be done by calling certain functions and their compositions.</p>
<p>Let me outline the main concepts I'll use to explain the core idea of this approach:</p>
<ol>
<li><p>Pure functions</p>
</li>
<li><p>Immutability</p>
</li>
<li><p>Function composition</p>
</li>
</ol>
<h3 id="heading-pure-functions">Pure Functions</h3>
<p>A function is called pure if it:</p>
<ul>
<li><p>returns the same value for the same input parameters;</p>
</li>
<li><p>has no side effects (changes to external values or entities).</p>
</li>
</ul>
<p>Here are a couple of examples to illustrate this:</p>
<pre><code class="language-javascript">let c = 10;
let s = 0;

// Pure function
function pureSum(a,b) {
  return a + b
}

// Impure function
function notPureSum (a) {
  s = a + c
  return s
}
</code></pre>
<p>The first function is pure because if we enter the same arguments, we'll get the same result. Also, this function doesn't change any global variables and doesn't mutate objects.</p>
<p>The second function doesn't meet the concept of purity on both points. It changes the external variable <code>s</code> and uses another external variable <code>c</code> for calculations, which can change.</p>
<p>Pure functions allow you to write more predictable code. An application can grow and a function with an implicit parameter that can change over time can lead to great difficulties in debugging and maintaining the codebase.</p>
<h3 id="heading-immutability">Immutability</h3>
<p>Immutability is a principle which states that data objects shouldn't change after they're created. To make changes to data, you need to create a new instance of it and then work with that new copy.</p>
<p>At first glance, it may seem that this approach limits flexibility in the development process and reduces the speed of the program. But in reality, if the language or runtime has optimisations for immutable data, adhering to this principle helps you avoid many errors and use parallel calculations.</p>
<p>Here's a fairly simple example: let's take a React component that renders a task in a to-do list. The state of the task is described by an object. And in order for React to correctly redraw the state of the task component when the user changes something in it, it's necessary to pass as the new state not the mutated old object, but a new instance of the object with the current state. Here is the example where an immutable object is used for the state of the to-do:</p>
<pre><code class="language-JS">import { useState } from "react";

function TodoItem() {
  const [todo, setTodo] = useState({
    text: "Write article",
    done: false
  });

  const toggleDone = () =&gt; {
    setTodo({
      ...todo,
      done: !todo.done
    });
  };

  return (
    &lt;div&gt;
      &lt;span&gt;
        {todo.text} — {todo.done ? "✅" : "❌"}
      &lt;/span&gt;
      &lt;button onClick={toggleDone}&gt;
        Toggle
      &lt;/button&gt;
    &lt;/div&gt;
  );
}

export default TodoItem;
</code></pre>
<p>Here we'll see that clicking on the button will lead to changes in the state of the to-do and will cause re-rendering of the component. But we could define the function <code>toggleDone()</code> in a different way using mutations of the object:</p>
<pre><code class="language-JS">const toggleDone = () =&gt; {
  todo.done = !todo.done;
  setTodo(todo);
};
</code></pre>
<p>With such an event handler, the effect will not work because the link to the object is still the same, even though the object itself was mutated.</p>
<h3 id="heading-function-composition">Function Composition</h3>
<p>Function composition involves using the result of one function as an argument for another function. Here's an example of a program that capitalises the first letter of each word:</p>
<pre><code class="language-javascript">function compose(f1, f2) {
  return function (str) {
    return f1(f2(str));
  }
}

function makeFirstCapital(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function upperEveryFirst(str) {
  return str.split(' ').map(makeFirstCapital).join(' ');
}

function lower(str) {
  return str.toLowerCase();
}

const capitalize = compose(upperEveryFirst, lower);
const string = 'this sTring should be Capitalized'

console.log(capitalize(string)) // 'This String Should Be Capitalized'
</code></pre>
<p>Here, we have defined the function <code>compose(f1, f2)</code>, which returns the composition of the functions passed to it in arguments. Next, we use this function to create the function <code>capitalise()</code>, which will capitalise only the first letters of words by composing the functions <code>lower()</code> and <code>upperEveryFirst()</code>. The first one is executed first and returns a string with all lowercase letters to the second function as an argument. The second function capitalises the first letter of each word.</p>
<p>In large projects and when complex calculations are required, such compositions can be larger, and the logic in them can be much richer. In such cases, this approach to calculations helps break down very large transformations into a series of relatively simple and compact functions that are applied one after the other. This makes it easier to develop, refactor, and debug code.</p>
<h2 id="heading-how-the-principles-of-fp-meet-their-incarnation-in-atomic-css">How the Principles of FP Meet Their Incarnation in Atomic CSS</h2>
<p>Now that we know a little about functional programming, let's try to answer the question: ‘What does Atomic CSS have to do with it?’ Let's draw an analogy between these approaches at the level of the principles described above.</p>
<h3 id="heading-what-is-atomic-css">What is Atomic CSS?</h3>
<p>But first, let's take a moment to explain what Atomic CSS is. It's a methodology of styling layouts in which we use small Atomic CSS rules, each of which performs a single action. These classes are called utilities. They usually apply one CSS property, but not necessarily just one.</p>
<p>For example, in the mlut framework, the <code>Bgc-red</code> utility corresponds to the <code>background-colour: red</code> property, and the <code>-Sz50p</code> utility corresponds to two properties at once: <code>width: 50%</code> and <code>height: 50%</code>.</p>
<p>Modern Atomic CSS frameworks, such as mlut and Tailwind, use a so-called JIT engine. This is a component that generates CSS based only on the utilities you used in your markup.</p>
<h3 id="heading-purity">Purity</h3>
<p>Purity in CSS is determined by which selectors, or more specifically, classes, are used to style elements. In clean CSS, the behaviour of an element should be determined solely by the classes that are attached to it in the <code>class</code> attribute. This means that, ideally, a stylesheet should not contain selectors such as <code>section</code> or <code>div &gt; ul</code>.</p>
<p>Firstly, they set too general rules, which are likely to be broken or supplemented in one part of the project or another. So when we style specific elements, we'll have to constantly keep these styles in mind in order to understand how to achieve the necessary styling without spoiling anything.</p>
<p>Secondly, the purity of CSS for each specific element is violated. Let's say we have the following markup:</p>
<pre><code class="language-html">&lt;button class="greeting"&gt;Hello!&lt;/button&gt;
&lt;div class="wrapper"&gt;
  &lt;button class="greeting"&gt;Hello!&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>With the CSS styles below:</p>
<pre><code class="language-CSS">.wrapper &gt; .greeting {
  background-color: green;
}
.greeting {
  background-color: red;
}
</code></pre>
<p>As a result, we get that the first button is red, and the nested one is green. It seems pretty harmless in this simple case, but it will cause inconvenience when the project structure grows significantly.</p>
<p>Here we see that the result of the <code>.greeting</code> class's custom styles depends on where the corresponding element is located. This is similar to a function that produces different results for the same input data depending on where it's called.</p>
<p>Atomic CSS allows you to avoid this effect. In this approach, in most cases, styles are applied only to those elements for which the corresponding classes are specified. If you need to make several identical elements, the same utility is specified in the <code>class</code> attribute of each such element.</p>
<p>A similar example can be rewritten in mlut like this:</p>
<pre><code class="language-HTML">&lt;button class="Bgc-red"&gt;Hello!&lt;/button&gt;
&lt;div&gt;
  &lt;button class="Bgc-green"&gt;Hello!&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>And JIT-engine will generate the following styles:</p>
<pre><code class="language-CSS">.Bgc-red {
  background-color: red;
}

.Bgc-green {
  background-color: green;
}
</code></pre>
<p>Here we can see that the styles of elements in this approach will depend only on the classes assigned to them.</p>
<p>It's worth noting here that the mlut syntax allows you to do things that deviate from the strict concept of CSS purity. Sometimes this is necessary to create relatively more complex effects.</p>
<p>Let's say we want to implement a card that changes the background colour of the button inside it when hovered. Then we would need to write the following in mlut:</p>
<pre><code class="language-html">&lt;div class="-Ctx"&gt;
  &lt;button class="^:h_Bgc-red"&gt;Greeting&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>CSS:</p>
<pre><code class="language-css">.-Ctx:hover .\^\:h_Bgc-red {
  background-color: red;
}
</code></pre>
<h3 id="heading-immutability-in-acss">Immutability in ACSS</h3>
<p>By CSS immutability, I mean that element styles are not rewritten. Immutability in ACSS means that utilities do not typically mutate each other.</p>
<p>For example, in BEM (Block Element Modifier), the main styles are set by a block or element, and a modifier mutates these styles. In other approaches that use combined selectors, such mutations occur more often and less explicitly.</p>
<p>Let me give you a simple example. Suppose we have a product card that can be in its default state or in a highlighted state. In BEM it would look like this:</p>
<pre><code class="language-html">&lt;div class="product-card"&gt;Card 1&lt;/div&gt;
&lt;div class="product-card product-card--selected"&gt;Card 2&lt;/div&gt;
&lt;div class="product-card"&gt;Card 3&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.product-card {
  background-color: red;
  padding: 5px;
}

.product-card--selected {
  background-color: green;
}
</code></pre>
<p>In this example, we see that the <code>product-card</code> class sets the default background colour of the card to red. And in order to somehow mark the selected card with a different background colour we have to add a modifier class that changes the colour from red to green. It does this by rewriting the <code>background-color</code> property, that is by mutating the block styles.</p>
<p>In the Atomic CSS approach, this problem is solved because utilities allow you to set CSS properties independently of each other and apply modifications without resorting to mutation.</p>
<p>Here's what this example would look like if you used mlut:</p>
<pre><code class="language-html">&lt;div class="P5 Bgc-red"&gt;Card 1&lt;/div&gt;
&lt;div class="P5 Bgc-green"&gt;Card 2&lt;/div&gt;
&lt;div class="P5 Bgc-red"&gt;Card 3&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.P5 {
  padding: 5px;
}

.Bgc-red {
  background-color: red;
}

.Bgc-green {
  background-color: green;
}
</code></pre>
<h3 id="heading-composition">Composition</h3>
<p>Functional programming makes extensive use of function composition. In Atomic CSS, function composition is analogous to utility composition when styling elements. Just as in FP we obtain complex behaviour through the sequential application of a set of simple functions, so in ACSS we can obtain non-trivial styling through a set of simple utilities.</p>
<p>As an example, I'll show a simple smiley face made using only Atomic CSS:</p>
<pre><code class="language-html">&lt;div class="-Sz150 Bgc-yellow Bdrd100p M-a Ps"&gt;
  &lt;div class="-Sz20p Bgc-gray Bdrd100p Ps-a T30p L20p"&gt;&lt;/div&gt;
  &lt;div class="-Sz20p Bgc-gray Bdrd100p Ps-a T30p R20p"&gt;&lt;/div&gt;
  &lt;div class="W50p H40p Bdb5;s;gray Bdrd100p Ps-a T40p L25p"&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="language-css">.-Sz150 {
  width: 150px;
  height: 150px;
}

.Bgc-yellow {
  background-color: yellow;
}

.Bdrd100p {
  border-radius: 100%;
}

.M-a {
  margin: auto;
}

.Ps {
  position: relative;
}

.-Sz20p {
  width: 20%;
  height: 20%;
}

.Bgc-gray {
  background-color: gray;
}

.Ps-a {
  position: absolute;
}

.T30p {
  top: 30%;
}

.L20p {
  left: 20%;
}

.R20p {
  right: 20%;
}

.W50p {
  width: 50%;
}

.H40p {
  height: 40%;
}

.Bdb5;s;gray {
  border-bottom: 5px solid gray;
}

.T40p {
  top: 40%;
}

.L25p {
  left: 25%;
}
</code></pre>
<p>This is how our result will look:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69844106d7e8f3ad9f2dd000/de19a0ca-af4e-45f4-8a90-1a0f55db7950.png" alt="Smiley face" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Thus, by applying the utilities one after another, we even made some small CSS art.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>To sum up, I will say that Atomic CSS truly embodies the basic principles of functional programming. Of course, not literally – but in the sense that is relevant for front-end developers and layout designers.</p>
<p>I would be happy to hear your additions and objections – it'll be interesting to read and think about them.</p>
<p>Finally, I would like to say: look at familiar things with a fresh perspective. And, as usual, I wish you success on your exciting journey of front-end development!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Flex in Tailwind CSS and Justify Flex Items ]]>
                </title>
                <description>
                    <![CDATA[ Hey there! If you're building modern web interfaces, chances are you've already fallen in love with Tailwind CSS for its speed and flexibility. One of the most powerful tools in Tailwind's arsenal is  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-flex-in-tailwind-css-and-justify-flex-items/</link>
                <guid isPermaLink="false">69b1975b6c896b0519a78b21</guid>
                
                    <category>
                        <![CDATA[ flexbox ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS Tutorial ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tailwind ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flex ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flex css ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Patel ]]>
                </dc:creator>
                <pubDate>Wed, 11 Mar 2026 16:24:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3522007b-55f0-44bf-9b6f-b0489d1a8774.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey there! If you're building modern web interfaces, chances are you've already fallen in love with Tailwind CSS for its speed and flexibility.</p>
<p>One of the most powerful tools in Tailwind's arsenal is its set of Flexbox utilities. Flexbox lets you create dynamic, responsive layouts without writing custom CSS, and Tailwind makes it incredibly intuitive with simple class names.</p>
<p>In this tutorial, we'll walk through everything you need to know about using Flexbox in Tailwind, from the basics to advanced patterns. Whether you're a beginner or looking to level up your layouts, by the end, you'll feel confident building anything from card grids to complex dashboards.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="https://preview.freecodecamp.org/69857b45ac030cc5d597ac21#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-flexbox">What is Flexbox?</a></p>
</li>
<li><p><a href="#heading-how-tailwind-css-makes-flexbox-easy-to-use">How Tailwind CSS Makes Flexbox Easy to Use</a></p>
</li>
<li><p><a href="#heading-how-to-use-flex-in-tailwind">How to use flex in Tailwind</a></p>
</li>
<li><p><a href="#heading-flex-item-sizing-basis-grow-amp-shrink">Flex Item Sizing: Basis, Grow &amp; Shrink</a></p>
</li>
<li><p><a href="#heading-controlling-flex-direction">Controlling Flex Direction</a></p>
</li>
<li><p><a href="#heading-fine-tuning-flexbox-layout">Fine-Tuning Flexbox Layout</a></p>
</li>
<li><p><a href="#heading-how-to-justify-and-align-flex-items-in-tailwind">How to Justify and Align Flex Items in Tailwind</a></p>
</li>
<li><p><a href="#heading-practice-flexbox-with-interactive-games">Practice Flexbox with Interactive Games</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion:</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before diving into Flexbox with Tailwind CSS, it helps to have a few basics in place so you can follow along comfortably.</p>
<p><strong>Basic Knowledge:</strong></p>
<ul>
<li><p>A foundational understanding of <strong>HTML</strong> (how elements and containers work).</p>
</li>
<li><p>Basic familiarity with <strong>CSS</strong> (especially properties like <code>display</code>, <code>width</code>, and <code>height</code>).</p>
</li>
<li><p>A general idea of how responsive design works (helpful but not required).</p>
</li>
</ul>
<p>You do <em>not</em> need to be a Flexbox expert, as we’ll cover the important concepts as we go.</p>
<p><strong>Tools You’ll Need:</strong></p>
<ul>
<li><p>A code editor like VS Code (or any editor you prefer)</p>
</li>
<li><p>A browser for testing layouts</p>
</li>
<li><p>A project with Tailwind CSS installed</p>
</li>
</ul>
<h2 id="heading-what-is-flexbox">What is Flexbox?</h2>
<p><strong>Flexbox (Flexible Box Layout)</strong> is a CSS layout model designed to make it easier to design flexible, responsive layouts without using floats or complicated positioning tricks.</p>
<p>Before Flexbox, aligning elements vertically, spacing items evenly, or making layouts adapt to different screen sizes was often frustrating and required hacks. Flexbox solves these problems by providing a simple and predictable way to control alignment, spacing, and ordering of elements inside a container.</p>
<p>The main concept is simple:</p>
<ul>
<li><p>You have a flex container</p>
</li>
<li><p>Inside it are flex items</p>
</li>
<li><p>The container controls how its items are laid out</p>
</li>
</ul>
<p>Once an element is set to <code>display: flex</code>, its children automatically become flex items.</p>
<pre><code class="language-css">.container {
  display: flex;
}
</code></pre>
<h2 id="heading-how-tailwind-css-makes-flexbox-easy-to-use">How Tailwind CSS Makes Flexbox Easy to Use</h2>
<p>Flexbox is powerful, but writing custom CSS for every layout can become repetitive and time-consuming. Tailwind CSS simplifies this by providing utility classes that map directly to Flexbox properties, allowing developers to build layouts quickly without writing custom CSS.</p>
<p>Instead of switching between HTML and CSS files, Tailwind lets you apply Flexbox behavior directly in your markup, making layouts more readable and faster to develop. It turns Flexbox's sometimes verbose properties into short, memorable utilities. No more remembering <code>justify-content: space-between;</code> just write <code>justify-between</code>.</p>
<p>Benefits:</p>
<ul>
<li><p>Responsive by default (add <code>md:</code>, <code>lg:</code>, and so on)</p>
</li>
<li><p>Composable (combine classes freely).</p>
</li>
<li><p>No custom CSS needed for most cases.</p>
</li>
</ul>
<h2 id="heading-how-to-use-flex-in-tailwind">How to Use Flex in Tailwind</h2>
<p>Flexbox is one of the most powerful layout systems in modern CSS, and Tailwind CSS makes it extremely approachable by exposing Flexbox behavior through simple utility classes. Instead of writing custom CSS, you compose layouts directly in your HTML using predefined classes.</p>
<ol>
<li><p><code>flex</code>: The <code>flex</code> Class is the foundation of Flexbox in Tailwind.</p>
</li>
<li><p><code>flex-1</code>: Allows the element to grow and shrink and forces it to take up the remaining available space.</p>
</li>
<li><p><code>flex-auto</code>: It makes an item flexible while respecting its content size – that is, it only grows and shrinks as needed.</p>
</li>
<li><p><code>flex-none</code>: Disables growing and shrinking for an item.</p>
</li>
</ol>
<p>Here is a basic example that shows where to place these classes:</p>
<pre><code class="language-xml">&lt;!-- this is the container --&gt;
&lt;div class="flex"&gt;
  &lt;!-- these are the items inside the container --&gt;
  &lt;div class="flex-1"&gt;Item 1&lt;/div&gt;
  &lt;div class="flex-auto"&gt;Item 2&lt;/div&gt;
  &lt;div class="flex-none"&gt;Item 3&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h2 id="heading-flex-item-sizing-basis-grow-amp-shrink">Flex Item Sizing: Basis, Grow, &amp; Shrink</h2>
<p>Three fundamental properties control how elements size themselves inside a flex container:</p>
<ul>
<li><p>flex-basis</p>
</li>
<li><p>flex-grow</p>
</li>
<li><p>flex-shrink</p>
</li>
</ul>
<p>Rather than thinking in fixed widths and heights, Flexbox uses a dynamic space-distribution model. Each flex item starts with an initial size, then grows or shrinks depending on the available space and the rules defined by these three properties.</p>
<h3 id="heading-flex-basis">flex-basis</h3>
<p><a href="https://tailwindcss.com/docs/flex-basis">flex-basis</a> controls the initial size of a flex item before <code>flex-grow</code> or <code>flex-shrink</code> kick in. Think of it as the item’s starting width or height (depending on flex direction).</p>
<pre><code class="language-xml">&lt;div class="flex ..."&gt;
	&lt;div class="... basis-1/5"&gt;01&lt;/div&gt;
  &lt;div class="...basis-4/5"&gt;02&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Here's what this makes:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770355749158/19071bac-8426-4602-a7ab-fcc0ecd62c95.png" alt="flex basis" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>flex-basis's most used utility classes are:</p>
<ul>
<li><p><code>basis-auto</code>: This means the item’s initial size is based on its <strong>content size or any explicitly defined width/height</strong>. It doesn't force a specific starting size. Instead, it respects intrinsic sizing.</p>
</li>
<li><p><code>basis-0</code>: This makes the item start at <strong>0 width (or height in column layouts)</strong> before space is distributed. It’s commonly used with <code>grow</code> to evenly distribute space regardless of content size.</p>
</li>
<li><p><code>basis-full</code>: The item initially takes up the <strong>entire width (or height)</strong> of the container before shrinking or wrapping.</p>
</li>
<li><p><code>basis-xs/md/lg/xl..</code>: Built-in values.</p>
</li>
<li><p><code>basis-&lt;fraction&gt;</code>: Giving a value with a dynamic such as 1/2, 4/5, and so on.</p>
</li>
<li><p><code>basis-&lt;number&gt;</code>: Uses Tailwind’s spacing scale (in <code>rem</code> units).</p>
</li>
<li><p><code>basis-[&lt;value&gt;]</code>: Syntax to set the basis based on a completely custom value.</p>
</li>
</ul>
<h3 id="heading-flex-grow">flex-grow</h3>
<p><a href="https://tailwindcss.com/docs/flex-grow">flex-grow</a> controls how much a flex item expands to fill extra space in the flex container. It determines how leftover space is distributed among flex items after their initial sizes.</p>
<pre><code class="language-xml">&lt;div class="flex ..."&gt;
	&lt;div class="... grow"&gt;01&lt;/div&gt;
  &lt;div class="...grow-0"&gt;02&lt;/div&gt;
  &lt;div class="...grow"&gt;03&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Here's what this creates:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770355790996/6c4f3c72-0ff9-4f5c-bab7-6e5bc1e9df2e.png" alt="flex grow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>flex-grow's most used utility classes are:</p>
<ul>
<li><p><code>grow</code>: The item will grow to take up available extra space. If multiple items use <code>grow</code>, they share space equally (unless different grow values are specified)..</p>
</li>
<li><p><code>grow-0</code>: The item will <strong>not expand</strong> beyond its initial size, even if extra space is available.</p>
</li>
<li><p><code>grow-&lt;number&gt;</code>: If one item has <code>grow-2</code> and another has <code>grow-1</code>, the first item gets <strong>twice as much extra space</strong> as the second.</p>
</li>
<li><p><code>grow-[&lt;value&gt;]</code>: Allows a custom grow value (e.g. grow-[3]).</p>
</li>
</ul>
<h3 id="heading-flex-shrink">flex-shrink</h3>
<p><a href="https://tailwindcss.com/docs/flex-shrink">flex-shrink</a> controls how much a flex item shrinks when there isn’t enough space in the flex container. It determines how items reduce their size relative to each other when the container overflows.</p>
<pre><code class="language-xml">&lt;div class="flex ..."&gt;
	&lt;div class="... grow shrink-0"&gt;01&lt;/div&gt;
  &lt;div class="...shrink"&gt;02&lt;/div&gt;
  &lt;div class="...grow shrink-0"&gt;03&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Here's what this creates:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770355844387/bd1b9e5c-0ae5-4e61-97f7-2cbfbc060ecf.png" alt="flex shrink" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>flex-shrink's most used utility classes are:</p>
<ul>
<li><p><code>shrink</code>: The item is allowed to shrink when necessary to prevent overflow.</p>
</li>
<li><p><code>shrink-0</code>: The item will <strong>not shrink</strong>, even if space becomes limited. This may cause overflow if other items cannot compensate.</p>
</li>
<li><p><code>shrink-&lt;number&gt;</code>: Sets proportional shrinking behavior. An item with <code>shrink-2</code> will shrink <strong>twice as much</strong> as one with <code>shrink-1</code>.</p>
</li>
<li><p><code>shrink-[&lt;value&gt;]</code>: Syntax to set a completely custom shrink value.</p>
</li>
</ul>
<h2 id="heading-controlling-flex-direction">Controlling Flex Direction</h2>
<p>In Tailwind CSS, the direction in which flex items are laid out is controlled using <a href="https://tailwindcss.com/docs/flex-direction">flex-direction utilities</a>. These utilities define whether items are placed horizontally or vertically, and in which order.</p>
<h3 id="heading-flex-row">flex-row</h3>
<p><code>flex-row</code> is the default flex direction in both CSS Flexbox and Tailwind. When it is applied, flex items are laid out <strong>horizontally</strong> along the main axis, starting from left to right (in left-to-right languages).</p>
<pre><code class="language-xml">&lt;div class="flex flex-row"&gt;
  &lt;div&gt;01&lt;/div&gt;
  &lt;div&gt;02&lt;/div&gt;
  &lt;div&gt;03&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>This outputs:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770356616341/4b3dbd4c-e437-49d0-a9b5-0576781ccfc3.png" alt="flex row default" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Use case:</strong> Navigation bars, horizontal button groups, toolbars.</p>
<h3 id="heading-flex-row-reverse">flex-row-reverse</h3>
<p>The <code>flex-row-reverse</code> utility lays out flex items horizontally, but in the opposite direction from right to left. While the visual order of items is reversed, the HTML source order remains unchanged, which is important for accessibility and screen readers.</p>
<pre><code class="language-xml">&lt;div class="flex flex-row-reverse"&gt;
  &lt;div&gt;01&lt;/div&gt;
  &lt;div&gt;02&lt;/div&gt;
  &lt;div&gt;03&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Output:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770356916275/1fad634a-c4a0-4417-a63e-0ed51d9f76f7.png" alt="flex row reverse" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Use case:</strong> Forms, cards, sidebars, vertical menus.</p>
<h3 id="heading-flex-col">flex-col</h3>
<p>The <code>flex-col</code> utility changes the flex direction to <strong>vertical</strong>, stacking items from top to bottom. In this case, the main axis runs vertically.</p>
<pre><code class="language-html">&lt;div class="flex flex-col"&gt;
  &lt;div&gt;01&lt;/div&gt;
  &lt;div&gt;02&lt;/div&gt;
  &lt;div&gt;03&lt;/div&gt;
&lt;/div&gt; 
</code></pre>
<p>Output:</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e0f2c2e490269cb30227a2b/cea4e846-a234-42e8-9509-2b166a5cf5c4.png" alt="cea4e846-a234-42e8-9509-2b166a5cf5c4" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Use case:</strong> Forms, cards, sidebars, vertical menus.</p>
<h3 id="heading-flex-col-reverse">flex-col-reverse</h3>
<p>In <code>flex-col-reverse</code>, items are stacked vertically, but in <strong>reverse order</strong>, starting from bottom to top (that is, vertically reverse order).</p>
<pre><code class="language-xml">&lt;div class="flex flex-col-reverse"&gt;
  &lt;div&gt;01&lt;/div&gt;
  &lt;div&gt;02&lt;/div&gt;
  &lt;div&gt;03&lt;/div&gt;
&lt;/div&gt; 
</code></pre>
<p>Here's the output:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770357355380/9d2d03b9-0c23-4360-ab82-2ccf8c1f32d9.png" alt="flex col reverse" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Use case:</strong> Chat messages, timelines, or when newer content should appear at the bottom.</p>
<p>Now you know a bit about managing flex-directions using tailwind utility classes. You can mange the stack depending on your needs.</p>
<h3 id="heading-responsive-control">Responsive Control</h3>
<p>Tailwind also allows you to change flex direction at different breakpoints so you’ll get a clean layout across different devices.</p>
<pre><code class="language-html">&lt;div class="flex flex-col md:flex-row"&gt;
	.....
&lt;/div&gt;
</code></pre>
<p>This stacks items vertically on small screens and switches to a horizontal layout on medium screens and above.</p>
<h2 id="heading-fine-tuning-flexbox-layout">Fine-Tuning Flexbox Layout</h2>
<p>Once a container is set to use Flexbox, the real power comes from controlling how flex items behave inside it. This is where properties like wrap, order, and gap become essential. These features determine how items flow, how they're visually arranged, and how much space exists between them.</p>
<p>In real-world layouts such as card grids, navigation menus, and dashboards, elements rarely fit perfectly in a single row or follow a fixed order across all screen sizes. Flexbox provides solutions to these challenges, and Tailwind CSS exposes them through simple, intuitive utility classes.</p>
<ul>
<li><p><strong>Wrap</strong> helps manage what happens when items exceed the available space</p>
</li>
<li><p><strong>Order</strong> allows you to rearrange elements visually without changing the HTML structure</p>
</li>
<li><p><strong>Gap</strong> controls spacing between items in a clean and predictable way</p>
</li>
</ul>
<p>Let’s dive into the depths of these fine-tuning flexbox properties.</p>
<h3 id="heading-flex-wrap"><a href="https://tailwindcss.com/docs/flex-wrap">flex-wrap</a></h3>
<p>By default, Flexbox tries to fit all items into one line. If there isn’t enough space, items will shrink to squeeze in. <code>flex-wrap</code> allows flex-items to move onto the next line instead of shrinking.</p>
<pre><code class="language-xml">&lt;div class="flex flex-wrap"&gt;
	&lt;div&gt;01&lt;div&gt;
	&lt;div&gt;02&lt;div&gt;
	&lt;div&gt;03&lt;div&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Utility classes</strong>:</p>
<ul>
<li><p><code>flex-nowrap</code>: (default) All items stay on one line.</p>
</li>
<li><p><code>flex-wrap</code>: Items wrap onto multiple lines.</p>
</li>
<li><p><code>flex-wrap-reverse</code>: Items wrap, but in reverse order.</p>
</li>
</ul>
<h3 id="heading-flex-order">flex-order</h3>
<p><a href="https://tailwindcss.com/docs/order">flex-order</a> controls the visual order of the stack/flex-items without changing the HTML structure. Each item has an order value. Items with lower order values appear first.</p>
<pre><code class="language-xml">&lt;div class="flex"&gt;
	&lt;div class="order-3 ..."&gt;01&lt;div&gt;
	&lt;div class="order-1 ..."&gt;02&lt;div&gt;
	&lt;div class="order-2 ..."&gt;03&lt;div&gt;
&lt;/div&gt;
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770357768674/b70b52ca-712e-409a-a0af-f73be8b2c649.png" alt="default order custom order" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Utility classes:</strong></p>
<ul>
<li><p><code>order-1</code> to <code>order-12</code>: Sets order value.</p>
</li>
<li><p><code>order-first</code>: Moves item to the start.</p>
</li>
<li><p><code>order-last</code>: Moves item to the end.</p>
</li>
<li><p><code>order-none</code>: Default order (0).</p>
</li>
</ul>
<h3 id="heading-gap">gap</h3>
<p><a href="https://tailwindcss.com/docs/gap">gap</a> controls the space between flex items, both rows and columns, without using margins. You can also apply for axis, which will help in giving space in both horizontal and vertical directions.</p>
<p>Here's an example of using it with a horizontal layout:</p>
<pre><code class="language-xml">&lt;div class="flex gap-8"&gt;
	&lt;div&gt;01&lt;div&gt;
	&lt;div&gt;02&lt;div&gt;
	&lt;div&gt;03&lt;div&gt;
&lt;/div&gt;
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770357860338/520e4c08-e334-4bee-9003-d6b22e857a18.png" alt="gap" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>And here's an example showing a vertical layout:</p>
<pre><code class="language-xml">&lt;div class="flex flex-col gap-y-5"&gt;
	&lt;div&gt;01&lt;div&gt;
	&lt;div&gt;02&lt;div&gt;
	&lt;div&gt;03&lt;div&gt;
&lt;/div&gt;
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770357897235/488b63a8-b520-4b3e-b83b-1b18a316f23d.png" alt="gap y 5" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Utility classes</strong>:</p>
<ul>
<li><p><code>gap-&lt;number&gt;</code>: Applies space (x-axis by default) between items according to the number, as shown in the example.</p>
</li>
<li><p><code>gap-[&lt;custom-property&gt;]</code>: You can apply a custom gap inside square brackets, such as <code>gap-[10px]</code>.</p>
</li>
<li><p><code>gap-x-&lt;number&gt;</code>: Provides horizontal spacing only.</p>
</li>
<li><p><code>gap-y-&lt;number&gt;</code>: Provides vertical spacing only.</p>
</li>
</ul>
<h3 id="heading-responsive-design">Responsive Design</h3>
<p>You can prefix&nbsp;the <code>gap</code>, <code>column-gap</code>,&nbsp;and&nbsp;<code>row-gap</code>&nbsp;utilities&nbsp;with a breakpoint variant, like&nbsp;<code>lg:</code>&nbsp;to only apply the utility at&nbsp;larger&nbsp;screen sizes and above. Here's an example:</p>
<pre><code class="language-xml">&lt;div class="flex gap-4 lg:gap-8 ..."&gt;
  &lt;!-- ... --&gt;
&lt;/div&gt;
</code></pre>
<h2 id="heading-how-to-justify-and-align-flex-items-in-tailwind">How to Justify and Align Flex Items in Tailwind</h2>
<p>If you're building interactive components like dropdowns, menus, or toolbars using Flexbox, alignment becomes even more important when handling keyboard navigation.</p>
<p>For example, when using arrow keys to navigate horizontally aligned items (<code>justify-between</code>, <code>justify-center</code>, and so on), proper spacing ensures a better user experience. You can explore how <a href="https://flyonui.com/docs/content/keyboard">Tailwind CSS keyboard</a> navigation works with <a href="https://flyonui.com/docs/content/keyboard/#arrow-keys">arrow keys</a> in FlyonUI.</p>
<p>FlyonUI provides accessible <a href="https://flyonui.com/docs/component/">Tailwind components</a> that integrate smoothly with Tailwind CSS, especially helpful when building flex-based navigation layouts.</p>
<p>Tailwind CSS offers a wide range of utility classes for aligning and justifying flex items, which can sometimes be confusing to differentiate. Below is a concise overview of these classes, along with practical examples.</p>
<h3 id="heading-justify-content">justify-content</h3>
<p><a href="https://tailwindcss.com/docs/justify-content">justify-content</a> is a flexbox property that controls how flex items are aligned along the main axis of a flex container. It decides how items are spaced inside a flex container. It is applied along with class <code>flex</code>.</p>
<p>Here's an example:</p>
<pre><code class="language-xml">&lt;div class="flex justify-start gap-2"&gt;
	&lt;div&gt;01&lt;div&gt;
	&lt;div&gt;02&lt;div&gt;
	&lt;div&gt;03&lt;div&gt;
&lt;/div&gt;
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770358388754/3fc9155b-067b-4ebd-9b75-037da1b34a00.png" alt="Justify Content" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Utility classes</strong>:</p>
<ul>
<li><p><code>justify-start</code>: Aligns items at the start of the container.</p>
</li>
<li><p><code>justify-center</code>: Centers items along the main axis.</p>
</li>
<li><p><code>justify-end</code>: Aligns items at the end of the container.</p>
</li>
<li><p><code>justify-between</code>: Adds space between items, pushing first and last items to the edges.</p>
</li>
<li><p><code>justify-around</code>: Adds equal space around each item.</p>
</li>
<li><p><code>justify-evenly</code>: Distributes items with equal spacing everywhere, including edges.</p>
</li>
</ul>
<h3 id="heading-align-items">Align Items</h3>
<p><a href="https://tailwindcss.com/docs/align-items">align-items</a> is a flexbox property that controls how flex items are aligned along the <strong>cross-axis</strong> of a flex container.</p>
<p>In a row-based flex container (<code>flex-row</code>, which is the default direction), the cross axis is <strong>vertical</strong>. That means <code>align-items</code> controls vertical alignment.</p>
<p>In a column-based container (<code>flex-col</code>), the cross axis becomes <strong>horizontal</strong>, so <code>align-items</code> controls horizontal alignment instead.</p>
<p>This property is applied alongside the <code>flex</code> class and is commonly used to align items consistently inside navigation bars, toolbars, cards, and forms.</p>
<pre><code class="language-xml">&lt;div class="flex items-start gap-2"&gt;
	&lt;div&gt;01&lt;/div&gt;
	&lt;div&gt;02&lt;/div&gt;
	&lt;div&gt;03&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770358590807/6440854e-822a-449d-ab22-fc417916bc45.png" alt="align text flexbox" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>Utility classes</strong>:</p>
<ul>
<li><p><code>items-start</code>: Aligns items to the start of the cross-axis (top in a row).</p>
</li>
<li><p><code>items-center</code>: Centers items vertically along the cross-axis.</p>
</li>
<li><p><code>items-end</code>: Aligns items to the end of the cross axis (bottom in a row).</p>
</li>
<li><p><code>items-baseline</code>: Aligns items based on their text baseline.</p>
</li>
<li><p><code>items-stretch</code>: Stretches items to fill the container (default behavior).</p>
</li>
</ul>
<h2 id="heading-practice-flexbox-with-interactive-games">Practice Flexbox with Interactive Games</h2>
<p>Reading documentation is important, but Flexbox really <em>clicks</em> when you practice it visually. One of the best ways to build strong Flexbox intuition is through interactive games that let you experiment with alignment, spacing, and direction in real time.</p>
<p>Here are two excellent games that will strengthen your Flexbox fundamentals and make Tailwind’s flex utilities feel second nature:</p>
<h3 id="heading-flexbox-froggy">Flexbox Froggy</h3>
<p>🔗 <a href="https://flexboxfroggy.com/">https://flexboxfroggy.com/</a></p>
<p>Flexbox Froggy is a fun and beginner-friendly game where you help frogs reach their lily pads using Flexbox properties. Each level introduces a new concept, like <code>justify-content</code>, <code>align-items</code>, <code>flex-direction</code>, and <code>flex-wrap</code>.</p>
<p><strong>Why it’s great:</strong></p>
<ul>
<li><p>Perfect for beginners</p>
</li>
<li><p>Visual feedback makes concepts easy to grasp</p>
</li>
<li><p>Covers core Flexbox properties step by step</p>
</li>
</ul>
<p>If you’re new to Flexbox, this is one of the best places to start.</p>
<h3 id="heading-flexbox-adventure-coding-fantasy">Flexbox Adventure (Coding Fantasy)</h3>
<p>🔗 <a href="https://codingfantasy.com/games/flexboxadventure">https://codingfantasy.com/games/flexboxadventure</a></p>
<p>Flexbox Adventure turns Flexbox learning into a role-playing game where you move your character by writing Flexbox rules. It focuses more on real-world layout thinking and helps reinforce how different properties work together.</p>
<p><strong>Why it’s great:</strong></p>
<ul>
<li><p>More challenging than Flexbox Froggy</p>
</li>
<li><p>Helps solidify intermediate concepts</p>
</li>
<li><p>Encourages problem-solving with Flexbox logic</p>
</li>
</ul>
<p>This is a great follow-up once you’re comfortable with the basics.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Flexbox is a core part of modern CSS layouts, and Tailwind CSS makes it even more powerful by turning Flexbox properties into simple, readable utility classes. By understanding utilities for direction, sizing, wrapping, spacing, ordering, and alignment, you can build responsive and flexible layouts without writing custom CSS.</p>
<p>And by understanding how properties like <code>flex</code>, <code>grow</code>, <code>shrink</code>, <code>basis</code>, <code>justify-*</code>, <code>items-*</code>, <code>gap</code>, when responsive variants work together, you can build layouts that are not only flexible and responsive but also maintainable and scalable.</p>
<p>Instead of wrestling with custom CSS, Tailwind allows you to express layout intent directly in your markup, keeping your workflow fast and predictable.</p>
<p>With these Flexbox fundamentals in your toolkit, you’re well-equipped to design clean, responsive interfaces using Tailwind CSS with confidence. 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the tailwind-sidebar NPM Package in Your React and Next.js Apps ]]>
                </title>
                <description>
                    <![CDATA[ These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces. In this article, you’ll learn what the tailwind-sidebar NPM package is, how ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tailwind-sidebar-npm-package-in-react-nextjs/</link>
                <guid isPermaLink="false">6967d8278e420016a8b8a729</guid>
                
                    <category>
                        <![CDATA[ Tailwind sidebar ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm packages ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hitesh Chauhan ]]>
                </dc:creator>
                <pubDate>Wed, 14 Jan 2026 17:53:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768413200090/f31cbba6-9b9e-4719-bc07-13fe98049d52.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces.</p>
<p>In this article, you’ll learn what the <code>tailwind-sidebar</code> NPM package is, how it works internally, and how to install and configure it in a real project. We’ll walk through setting up a responsive sidebar using Tailwind CSS, explore its key features with practical examples, and see how you can customize and control the sidebar behavior to fit different layouts and screen sizes.</p>
<p>If you’re building a React or Next.js application and want a lightweight yet powerful sidebar solution, <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar"><strong>tailwind-sidebar</strong></a> is an excellent choice.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>Basic knowledge of JavaScript (ES6+)</p>
</li>
<li><p>Familiarity with React fundamentals (components, props, JSX)</p>
</li>
<li><p>Basic understanding of Next.js project structure and routing</p>
</li>
<li><p>Experience using npm or yarn for package installation</p>
</li>
<li><p>Working knowledge of Tailwind CSS</p>
</li>
</ul>
<h2 id="heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</h2>
<p>The tailwind-sidebar NPM package is a modern, developer-friendly sidebar component that’s built entirely with Tailwind CSS. It’s designed to help developers create responsive, customizable, and accessible sidebars without the overhead of heavy UI frameworks.</p>
<h3 id="heading-understanding-tailwind-sidebar">Understanding Tailwind Sidebar</h3>
<p><code>tailwind-sidebar</code> is a lightweight utility package designed to simplify building responsive sidebars using Tailwind CSS. Instead of manually handling sidebar state, transitions, and responsive behavior, the package provides a small JavaScript layer that works alongside Tailwind’s utility classes.</p>
<p>At its core, the package toggles Tailwind classes to control whether the sidebar is open or closed. This makes it framework-agnostic - it works with plain HTML, as well as frameworks like React, Vue, or Next.js, as long as Tailwind CSS is available.</p>
<p>Because it relies on Tailwind utilities rather than custom CSS, the sidebar stays easy to customize, extend, and maintain.</p>
<h3 id="heading-what-is-tailwind-css">What is Tailwind CSS?</h3>
<p>Tailwind CSS is a utility-first CSS framework that lets you build modern, responsive user interfaces directly in your markup. Instead of predefined components, Tailwind provides low-level utility classes that give you full design control without leaving your HTML.</p>
<p>Tailwind Sidebar is built on top of Tailwind CSS, offering a clean, flexible, and highly customizable sidebar solution for modern web applications.</p>
<h2 id="heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</h2>
<h3 id="heading-optimized-performance">Optimized Performance</h3>
<p>Tailwind Sidebar relies on utility classes instead of heavy JavaScript logic. This means that it delivers fast load times and smooth interactions, and is ideal for performance-critical applications.</p>
<h3 id="heading-developer-friendly">Developer-Friendly</h3>
<p>It doesn’t have any complex configuration or component APIs. If you know Tailwind CSS, you already know how to customize the sidebar.</p>
<h3 id="heading-easy-maintenance">Easy Maintenance</h3>
<p>With a simple and predictable structure, updates and custom changes are straightforward, making it suitable for both small projects and large-scale applications.</p>
<h3 id="heading-growing-tailwind-community">Growing Tailwind Community</h3>
<p>Tailwind CSS has a massive and active community. This means better tooling, regular updates, and a wealth of learning resources to support your development workflow.</p>
<h2 id="heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</h2>
<p>This section will walk you through installing and setting up <code>tailwind-sidebar</code> in a React and Next.js application. You’ll learn how to add a sidebar, create menus, add a logo, and customize navigation.</p>
<h3 id="heading-step-1-install-tailwind-sidebar">Step 1: Install tailwind-sidebar</h3>
<p>To begin, you’ll need to install <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar">tailwind Sidebar</a> into your React and Next.js project. You can do this using either npm or yarn.</p>
<h4 id="heading-using-npm">Using npm:</h4>
<pre><code class="lang-javascript">npm i tailwind-sidebar
</code></pre>
<h4 id="heading-using-yarn">Using yarn:</h4>
<pre><code class="lang-javascript">yarn add tailwind-sidebar
</code></pre>
<p>This will add <code>tailwind-sidebar</code> and its dependencies to your project.</p>
<h3 id="heading-step-2-import-the-tailwind-sidebar-component">Step 2: Import the Tailwind Sidebar Component</h3>
<p>Once the package is installed, you can import the necessary components from tailwind-sidebar into your project. These components will allow you to customize the sidebar with menus, submenus, and even a logo.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {

  AMSidebar,

  AMMenuItem,

  AMMenu,

  AMSubmenu,

  AMLogo,

} <span class="hljs-keyword">from</span> <span class="hljs-string">"tailwind-sidebar"</span>;
</code></pre>
<h4 id="heading-adding-styles-to-tailwind-sidebar">Adding Styles to Tailwind Sidebar</h4>
<p>To use the default styles of tailwind-sidebar, you need to import its CSS file at the top of your project:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"tailwind-sidebar/styles.css"</span>;
</code></pre>
<h3 id="heading-step-3-routing-setup-for-react-and-nextjs">Step 3: Routing Setup for React and Next.Js</h3>
<p>To enable navigation inside <code>tailwind-sidebar</code> components like <code>AMMenuItem</code> or <code>AMLogo</code>, you need to pass a link component from either react-router or next/link using the component prop inside the corresponding component, like what’s shown in the below example:</p>
<p>If you're using <strong>React</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>

     <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart

      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
         <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/"  // Passing link to component for routing
          badge={true}
          badgeType="default"
          badgeColor={"bg-secondary"}
          isSelected={true}
        &gt;
          {/* text for your link */}
          Link Text
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">AMSidebar</span>&gt;</span></span>
</code></pre>
<p>And if you're using <strong>Next.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span>  Link  <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> // <span class="hljs-attr">Passing</span> <span class="hljs-attr">link</span> <span class="hljs-attr">to</span> <span class="hljs-attr">component</span> <span class="hljs-attr">for</span> <span class="hljs-attr">routing</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
        AdminMart
      <span class="hljs-tag">&lt;/<span class="hljs-name">Logo</span>&gt;</span></span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
          <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/tes"
          component={Link}
          isSelected={true}
        &gt;
           Link Text {/* text for your link */}
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
    &lt;/AMSidebar&gt;
</code></pre>
<h3 id="heading-step-4-initializing-the-sidebar">Step 4: Initializing the Sidebar</h3>
<p>Now we’ll set up the <code>AMSidebar</code> component in your application. You can set the width of the sidebar using the <code>width</code> prop. Here’s a simple example:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt; <span class="hljs-comment">// pass the width you want your sidebar to have</span>

{<span class="hljs-comment">/* Sidebar Content Goes Here */</span>}

&lt;/AMSidebar&gt;
</code></pre>
<p>This initializes the sidebar with a width of <code>270px</code>. You can adjust this width based on your design requirements.</p>
<h3 id="heading-step-5-adding-a-logo-to-the-sidebar">Step 5: Adding a Logo to the Sidebar</h3>
<p>You can add a logo inside the sidebar by using the <code>AMLogo</code> component. To do so, you can provide an <code>img</code> prop to link to a CDN logo image. You can also make the logo clickable by passing a navigation link using the <code>component</code> and <code>href</code> props. Here’s how you can include a logo:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
<span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
<span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> 
&gt;</span>        
Adminmart
<span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span></span>

&lt;/AMSidebar&gt;
</code></pre>
<p>In this example, we’ve added a logo from a CDN using the <code>img</code> prop, used the <code>component</code> prop to pass the <code>Link</code>, and set the navigation path to <code>(/)</code> homepage using the <code>href</code> prop and set the text “AdminMart” as the name of the application.</p>
<h3 id="heading-step-6-creating-a-menu-inside-the-sidebar">Step 6: Creating a Menu Inside the Sidebar</h3>
<p>Now let’s create a menu inside the sidebar using the <code>AMMenu</code> component. You can specify a submenu heading using the <code>subHeading</code> prop. Inside the <code>AMMenu</code>, you can add <code>AMMenuItem</code> components for each item.</p>
<p>You can also provide a <code>link</code> prop along with the <code>component</code> prop to the <code>AMMenuItem</code> to turn the item into a clickable link.</p>
<p>Here’s how you can structure the menu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar
 width={<span class="hljs-string">"270px"</span>}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/"</span>  <span class="hljs-attr">badge</span>=<span class="hljs-string">”true”</span>  <span class="hljs-attr">isSelected</span>=<span class="hljs-string">{true}</span> &gt;</span>
      Modern
    <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>eCommerce<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Analytical<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>We’ve added a <code>AMMenu</code> with the heading “HOME”.</p>
</li>
<li><p>The first <code>AMMenuItem</code> has a <code>link</code> prop, so clicking it will navigate to the homepage <code>(/)</code>.</p>
</li>
<li><p>The second and third <code>AMMenuItem</code> components are simple text items without links.</p>
</li>
</ul>
<p>You can use the <code>badge="true"</code> prop to indicate a badge or notification on the <strong>AMMenuItem</strong>. The <code>isSelected={true}</code> prop marks this menu item as currently selected or active (though you can customize this feature according to your needs).</p>
<h3 id="heading-step-7-adding-submenus-optional">Step 7: Adding Submenus (Optional)</h3>
<p>To add submenus inside the main menu, use the <code>AMSubmenu</code> component. The <code>AMSubmenu</code> can be nested inside the <code>AMMenu</code> component and contains its own set of <code>AMMenuItem</code>. Use the <code>title</code> prop to set the submenu heading</p>
<p>Here’s an example of adding a submenu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar  width={<span class="hljs-string">"270px"</span>}&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SERVICES"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>   <span class="hljs-attr">link</span>=<span class="hljs-string">"/web-development"</span>&gt;</span>Web Development<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/seo-services"</span>&gt;</span>SEO Services<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Marketing"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/digital-marketing"</span>&gt;</span>Digital Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/content-marketing"</span>&gt;</span>Content Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>A submenu under the "Marketing" heading is added inside the "SERVICES" menu.</p>
</li>
<li><p>The submenu contains two <code>AMMenuItem</code> with links to different service pages.</p>
</li>
</ul>
<h2 id="heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</h2>
<h3 id="heading-utility-first-amp-lightweight">Utility-First &amp; Lightweight</h3>
<p>Tailwind Sidebar is built entirely using Tailwind CSS utility classes. This means there’s no heavy JavaScript logic or extra styling framework, keeping your bundle size small and performance fast.</p>
<p><strong>Code Example:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"bg-gray-900 text-white"</span>&gt;
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
 &lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>bg-gray-900 text-white</code>: Applies a dark background and white text <em>directly</em> via Tailwind classes (no separate CSS file).</p>
</li>
<li><p><code>width="260px"</code>: The component prop sets the sidebar width. Here, it shows how Tailwind utilities combine with props to control layout.</p>
</li>
</ul>
<p>Because all spacing and colors are from Tailwind classes, you don’t need additional custom styles.</p>
<h3 id="heading-fully-responsive-design">Fully Responsive Design</h3>
<p>The sidebar adapts seamlessly to different screen sizes. Whether you’re building for desktop, tablet, or mobile, Tailwind Sidebar ensures a smooth and consistent navigation experience.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"hidden md:block"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>hidden md:block</code>: Uses Tailwind <em>responsive utility classes</em> to hide the component (<code>hidden</code>) on mobile screens and show it starting at the <code>md</code> breakpoint (<code>md:block</code>).</li>
</ul>
<p>This pattern is Tailwind’s common way of controlling visibility across breakpoints without media queries.</p>
<h3 id="heading-highly-customizable">Highly Customizable</h3>
<p>Tailwind Sidebar allows full customization of colors, hover states, spacing, and typography directly through Tailwind classes. You can customize layout and animations – all without touching custom CSS files.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript"> &lt;AMMenuItem className=<span class="hljs-string">"hover:bg-blue-600 rounded-lg px-4 py-2”&gt;
      Analytics
  &lt;/AMMenuItem&gt;</span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>hover:bg-blue-600</code>: When you hover the menu item, the background changes to blue, purely via Tailwind.</p>
</li>
<li><p><code>rounded-lg</code>: Adds rounded corners.</p>
</li>
<li><p><code>px-4 py-2</code>: Controls horizontal (<code>px</code>) and vertical (<code>py</code>) padding to adjust spacing.</p>
</li>
</ul>
<p>Together, these utilities show how Tailwind gives control of design details directly at the HTML/JSX level.</p>
<h3 id="heading-integration-with-react-amp-nextjs">Integration with React &amp; Next.Js:</h3>
<p>Tailwind Sidebar seamlessly integrates with both React &amp; Next.js, offering a familiar and efficient development experience.</p>
<p>The sidebar works natively with both <strong>React Router</strong> and <strong>Next.js routing</strong> by passing the <code>Link</code> component.</p>
<pre><code class="lang-javascript">React Example
{
  <span class="hljs-comment">/* if you are using react then import link from  */</span>
}

<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
            Dashboard 
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>

Next.js Example
{
  <span class="hljs-comment">/* if you are using nextjs then import link from  */</span>
}

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
         Dashboard
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>component={Link} link="/dashboard"</code>: Shows how you pass your framework’s routing component into <code>AMMenuItem</code>, turning it into a real navigational link.</li>
</ul>
<p>This means Tailwind Sidebar adapts to both React Router and Next.js routing without boilerplate.</p>
<h3 id="heading-menu-amp-submenu-support">Menu &amp; Submenu Support</h3>
<p>Organize your navigation with:</p>
<ul>
<li><p>Main menu items</p>
</li>
<li><p>Nested submenus</p>
</li>
</ul>
<p>This makes it easy to manage complex navigation structures while keeping the UI clean and intuitive.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMMenu subHeading=<span class="hljs-string">"MANAGEMENT"</span>&gt;
             <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Users<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
                <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Reports"</span>&gt;</span>
                   <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Sales<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Revenue<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span></span>
&lt;/AMMenu&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>subHeading="SERVICES"</code>: Adds a labeled grouping for menu items.</p>
</li>
<li><p>The nested <code>&lt;AMSubmenu&gt;</code> demonstrates how nested navigation is rendered and structured in JSX.</p>
</li>
</ul>
<p>This example clearly shows hierarchical menus without additional CSS – structure and Tailwind classes handle layout.</p>
<h3 id="heading-icon-support">Icon Support</h3>
<p>The sidebar comes with built-in support for icons, allowing developers to enhance the visual appeal and usability of their application. Developers can use any icon library and provide the icon component.</p>
<p><strong>Example using lucide-react:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Home } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{18}</span> /&gt;</span>}&gt;
     Home
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>icon={&lt;Home size={18} /&gt;}</code>: Here, an icon component is passed as a prop.</p>
</li>
<li><p>You control the size directly via the icon library (Lucide here) and Tailwind handles spacing/placement next to text.</p>
</li>
</ul>
<p>This illustrates how icons and text combine in the sidebar component.</p>
<h3 id="heading-smooth-transitions">Smooth Transitions</h3>
<p>Tailwind Sidebar provides built-in animation support through the animation prop. When enabled, menu items and submenus animate smoothly, improving the overall user experience without requiring custom CSS or JavaScript.</p>
<p><strong>Example Usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"270px"</span> animation={<span class="hljs-literal">true</span>}&gt;
           <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SETTINGS"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Profile<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Security<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>animation={true}</code>: Enables built-in animation support.</li>
</ul>
<p>The example shows how adding this prop triggers smooth transitions defined by the component itself (still relying on Tailwind utilities internally). You don’t have to write CSS keyframes or transition utilities manually.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>You have now successfully integrated a fully functional sidebar in your React and Next.js application using <code>tailwind-sidebar</code>. You can further customize the sidebar by:</p>
<ul>
<li><p>Modifying the width and design.</p>
</li>
<li><p>Adding more submenus, menu items, or icons.</p>
</li>
<li><p>Using links to navigate between pages.</p>
</li>
</ul>
<p>This setup provides a flexible, responsive, and easy-to-use sidebar, which is perfect for most web applications.</p>
<p>If you want to see how this kind of sidebar fits into a real dashboard layout, you can explore an open-source Tailwind CSS admin template at <a target="_blank" href="https://tailwind-admin.com/">https://tailwind-admin.com/</a>.</p>
<h3 id="heading-try-it-out">Try It Out:</h3>
<p>You can view the working demo of the tailwind-sidebar here: <a target="_blank" href="https://tailwind-admin-react-free.netlify.app/"><strong>View Demo</strong></a><strong>.</strong></p>
<p><strong>Note:</strong> in this tutorial, we utilized <code>lucide-react</code> to construct this sidebar. Feel free to choose an alternative library or use different icons based on your specific requirements.</p>
<h3 id="heading-other-sidebar-npm-packages">Other Sidebar NPM Packages</h3>
<p>You can also try <a target="_blank" href="https://www.npmjs.com/package/nextjs-tailwind-sidebar"><strong>Next.js Tailwind Sidebar</strong></a> – a simple and responsive sidebar built for Next.js, and <a target="_blank" href="https://www.npmjs.com/package/react-tailwind-sidebar"><strong>React Tailwind Sidebar</strong></a> – a lightweight Tailwind-based sidebar for React applications.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate Tailwind with Electron – With Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you’ll learn how to integrate Tailwind CSS with Electron to build stylish, responsive desktop applications. You’ll set up Tailwind in an Electron project, configure your project, style the components, and optimize the development wor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/integrate-tailwind-with-electron/</link>
                <guid isPermaLink="false">689cc9ce08eeb0d54c95cff1</guid>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Electron ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abhijeet Dave ]]>
                </dc:creator>
                <pubDate>Wed, 13 Aug 2025 17:22:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755102784797/0a2d19f0-3539-47c6-a29d-68fab3d430ba.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you’ll learn how to integrate Tailwind CSS with Electron to build stylish, responsive desktop applications.</p>
<p>You’ll set up Tailwind in an Electron project, configure your project, style the components, and optimize the development workflow. This is perfect for developers who are looking to combine Tailwind's utility-first CSS framework with Electron's cross-platform capabilities.</p>
<h2 id="heading-table-of-content">Table of Content</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-a-quick-overview-of-electron">A Quick Overview of Electron</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-tailwind-css">What is Tailwind CSS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-electron-and-tailwind-work-so-well-together">Why Electron and Tailwind Work So Well Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-well-build">What We’ll Build?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-initialize-an-electron-project">How to Initialize an Electron Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-tailwind-css-with-electron">How to Integrate Tailwind CSS with Electron?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-a-tailwind-component-library-a-practical-example">How to Use a Tailwind Component Library – a Practical Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-test-flyonui-js-components">Let’s Test FlyonUI JS Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-a-quick-overview-of-electron">A Quick Overview of Electron</h2>
<p><a target="_blank" href="https://www.electronjs.org/"><strong>Electron</strong></a> is a framework that lets developers create desktop applications for Windows, macOS, and Linux using familiar web technologies like HTML, CSS, and JavaScript, along with Node.js for backend features.</p>
<p>It's open-source, MIT-licensed, and completely free to use – whether you're building personal projects or commercial apps.</p>
<p>In this guide, we’ll look at why so many developers and companies choose Electron for building modern desktop apps.</p>
<h2 id="heading-what-is-tailwind-css"><strong>What is Tailwind CSS?</strong></h2>
<p><a target="_blank" href="https://tailwindcss.com/"><strong>Tailwind CSS</strong></a> is essentially a utility-first framework for styling web interfaces. Unlike frameworks that provide full-blown, pre-designed UI components, Tailwind offers a comprehensive set of single-purpose utility classes. You apply these classes directly to your HTML elements, which means you can rapidly build out custom layouts and designs without diving into separate CSS files.</p>
<p>The big advantage? Precision and flexibility – you can assemble unique, responsive interfaces by combining these classes however you see fit, all while keeping you markup lean and maintainable.</p>
<h2 id="heading-why-electron-and-tailwind-work-so-well-together"><strong>Why Electron and Tailwind Work So Well Together</strong></h2>
<p>Electron uses HTML, CSS, and JavaScript to build desktop applications. Essentially, it runs a web app in a desktop shell. This makes it easy to integrate modern frontend tools like Tailwind CSS.</p>
<p>Tailwind's utility-first approach allows you to style interfaces directly in you HTML, which can speed up UI development. Instead of writing custom styles or managing large CSS files, you apply predefined classes directly to elements. This aligns well with Electron's structure, where layout and styles are tightly connected within the same HTML environment.</p>
<p>Tailwind also provides sensible defaults and a consistent design system out of the box. This helps you prototype and build visually consistent desktop apps faster. While some familiarity with CSS is still helpful, Tailwind's approach can reduce the overhead of setting up and managing styles, especially in smaller or design-light projects.</p>
<p>Together, Electron and Tailwind offer a straightforward path to building desktop apps.</p>
<h2 id="heading-what-well-build">What We’ll Build?</h2>
<p>In this tutorial, we will create a basic Electron desktop app styled with Tailwind CSS and improved with FlyonUI components. You don’t need any prior experience with Electron or Tailwind.</p>
<p>By the end of the guide, you'll have:</p>
<ul>
<li><p>A working desktop window (Electron)</p>
</li>
<li><p>Styled UI with Tailwind CSS</p>
</li>
<li><p>A reusable button component</p>
</li>
<li><p>A fully functional modal dialog powered by FlyonUI</p>
</li>
</ul>
<p>This will provide a solid base for building more complex apps in the future.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before we dive in, make sure you have the following:</p>
<ul>
<li><p><strong>Basic knowledge of HTML, CSS, and JavaScript</strong>. You don’t need to be an expert, but understanding how to structure HTML and use basic JavaScript will help you follow along.</p>
</li>
<li><p><strong>Familiarity with Node.js and npm</strong>. We'll use npm (Node Package Manager) to install dependencies and run build commands.</p>
</li>
<li><p><strong>Node.js installed on your machine</strong>. You can download it from <a target="_blank" href="http://nodejs.org">nodejs.org</a><a target="_blank" href="https://nodejs.org/">.</a></p>
</li>
<li><p><strong>A code editor</strong>. I recommend <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code.</a></p>
</li>
<li><p><strong>Terminal / Command Line Access</strong>. You’ll need to run commands in the terminal to set things up.</p>
</li>
</ul>
<h2 id="heading-how-to-initialize-an-electron-project"><strong>How to Initialize an Electron Project</strong></h2>
<p>Let’s set up a basic Electron app from scratch. This will launch a simple desktop window that loads an HTML file, serving as the foundation for your UI.</p>
<h3 id="heading-1-create-your-project-folder">1. <strong>Create Your Project Folder</strong></h3>
<p>First, open your terminal and create a new project folder:</p>
<pre><code class="lang-bash">mkdir my-electron-app
<span class="hljs-built_in">cd</span> my-electron-app
</code></pre>
<p>This creates a new folder called <code>my-electron-app</code> and changes the directory to it. This folder will be your project workspace.</p>
<h3 id="heading-2-install-electron">2. <strong>Install Electron</strong></h3>
<p>Next, install Electron as a development dependency:</p>
<pre><code class="lang-bash">npm install electron --save-dev
</code></pre>
<p>This will add Electron to your project’s <code>node_modules</code> and update your <code>package.json</code> file.</p>
<p>This command installs Electron as a development dependency. Electron allows you to build desktop apps across different platforms using web technologies like HTML, CSS, and JavaScript.</p>
<h3 id="heading-3-configure-packagejson">3. <strong>Configure</strong> <code>package.json</code></h3>
<p>Update your <code>package.json</code> to point to a file named <code>main.js</code>, and add a script for easily starting the app.</p>
<p><strong>Edit your</strong> <code>package.json</code> like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"main"</span>: <span class="hljs-string">"main.js"</span>,
<span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"electron ."</span>
}
</code></pre>
<ul>
<li><p><code>"main"</code> defines the entry point of your Electron app (the main process).</p>
</li>
<li><p><code>"start"</code> is a shortcut to launch the app using <code>npm start</code>.</p>
</li>
</ul>
<h3 id="heading-4-create-mainjs">4. <strong>Create</strong> <code>main.js</code></h3>
<p>Create a file named <code>main.js</code> in your root folder and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { app, BrowserWindow } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"electron/main"</span>);

<span class="hljs-keyword">const</span> createWindow = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> win = <span class="hljs-keyword">new</span> BrowserWindow({
    <span class="hljs-attr">width</span>: <span class="hljs-number">800</span>,
    <span class="hljs-attr">height</span>: <span class="hljs-number">600</span>,
  });

  win.loadFile(<span class="hljs-string">"index.html"</span>);
};

app.whenReady().then(<span class="hljs-function">() =&gt;</span> {
  createWindow();

  app.on(<span class="hljs-string">"activate"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (BrowserWindow.getAllWindows().length === <span class="hljs-number">0</span>) {
      createWindow();
    }
  });
});

app.on(<span class="hljs-string">"window-all-closed"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (process.platform !== <span class="hljs-string">"darwin"</span>) {
    app.quit();
  }
});
</code></pre>
<p>This is your <strong>main process</strong>. It creates and manages the app window while loading your HTML file.</p>
<h3 id="heading-5-create-indexhtml-renderer-process">5. <strong>Create</strong> <code>index.html</code> <strong>(Renderer Process)</strong></h3>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"Content-Security-Policy"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"default-src 'self'; script-src 'self'"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Hello from Electron renderer!<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello from Electron renderer!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"info"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./renderer.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This is the <strong>frontend (renderer)</strong> of your Electron app. You can use standard HTML, CSS, and JavaScript here, just like in a browser.</p>
<blockquote>
<p>Optional: Create a renderer.js file if you want to add JavaScript interactivity.</p>
</blockquote>
<h3 id="heading-6-run-the-electron-app">6. <strong>Run the Electron App</strong></h3>
<p>Now, create a file called <code>main.js</code> in the root of your project. This is the main process that starts your Electron app:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>This command runs the app using the script you set up in <code>package.json</code>. A desktop window should open, displaying your HTML content.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754292319523/58168908-40ed-4aca-920f-fbab7435b580.webp" alt="hello form electron renderer" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-integrate-tailwind-css-with-electron"><strong>How to Integrate Tailwind CSS with Electron?</strong></h2>
<p>For this guide, we’ll be using the <a target="_blank" href="https://tailwindcss.com/docs/installation/tailwind-cli">Tailwind CLI</a> approach.</p>
<p>The Tailwind CLI is a command-line tool that allows you to generate and compile Tailwind utility classes directly into a CSS file. You don’t need complex build tools like Webpack or PostCSS. This makes it perfect for simple projects like Electron apps, where you’d want minimal setup and quick styling. The CLI also has a <code>--watch</code> mode that automatically rebuilds CSS when files change. This feature helps you stay productive.</p>
<h3 id="heading-1-install-tailwind-css">1. <strong>Install Tailwind CSS</strong></h3>
<p>First, install Tailwind CSS. Make sure Node.js is installed as well, then run:</p>
<pre><code class="lang-bash">npm install tailwindcss @tailwindcss/cli
</code></pre>
<p>This command installs Tailwind and its CLI tool as a development dependency in your project. We’ll use the CLI to build and monitor our Tailwind styles.</p>
<h3 id="heading-2-set-up-tailwind-css">2. <strong>Set Up Tailwind CSS</strong></h3>
<p>Create a <code>input.css</code> file with the following content to set up Tailwind:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss"</span>;
</code></pre>
<p>This line instructs Tailwind to generate all its utility classes into one output file, which we’ll compile next.</p>
<h3 id="heading-3-add-a-build-script">3. <strong>Add a Build Script</strong></h3>
<p>Then update your <code>package.json</code> to include a build script:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {  
 <span class="hljs-attr">"watch:css"</span>:<span class="hljs-string">"npx @tailwindcss/cli -i ./input.css -o ./output.css --watch"</span>,
}
</code></pre>
<p>This command watches your <code>input.css</code> file and continuously builds a compiled CSS file (<code>output.css</code>) whenever it detects changes. You’ll include this file in your HTML.</p>
<h3 id="heading-4-link-outputcss-in-indexhtml">4. <strong>Link</strong> <code>output.css</code> <strong>in</strong> <code>index.html</code></h3>
<p>Open your <code>index.html</code> file and add this inside the <code>&lt;head&gt;</code></p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./output.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
</code></pre>
<p>Then compile Tailwind CSS with this command:</p>
<pre><code class="lang-bash">npm run watch:css
</code></pre>
<p>This step includes the compiled Tailwind styles in your Electron app UI.</p>
<h3 id="heading-5-compile-tailwind-css">5. <strong>Compile Tailwind CSS</strong></h3>
<p>Run this script to start watching for changes and generate your CSS:</p>
<pre><code class="lang-bash">npm run watch:css
</code></pre>
<p>Keep this process running in a terminal window. It updates <code>output.css</code> live as you work.</p>
<h3 id="heading-6-update-the-ui-with-tailwind-classes">6. <strong>Update the UI with Tailwind Classes</strong></h3>
<p>Replace your <code>&lt;body&gt;</code> content with this example layout:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-center justify-center h-screen bg-gray-100"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-purple-600 text-white hover:bg-purple-700 focus:outline-hidden cursor-pointer focus:bg-purple-700 disabled:opacity-50 disabled:pointer-events-none"</span>&gt;</span>
    Hello Tailwindcss
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<h3 id="heading-7-run-the-electron-app">7. <strong>Run the Electron App</strong></h3>
<p>Run the electron server with this command:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>Your Electron window should now open with a well-styled button in the center, thanks to Tailwind CSS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754374369750/f79a6c6d-bc59-4a2b-a6eb-f3b63f8bccc4.png" alt="run electron server" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-use-a-tailwind-component-library-a-practical-example">How to Use a Tailwind Component Library – a Practical Example</h2>
<p>We are going to use <a target="_blank" href="https://flyonui.com/">FlyonUI</a>, an open source Tailwind component library. It includes a mix of utility classes in addition to JavaScript plugins. FlyonUI draws ideas from daisyUI but also from Preline, and combines flexibility and simplicity. It also helps you build interfaces that respond well and appear consistent.</p>
<h3 id="heading-1-install-flyonui">1. <strong>Install FlyonUI</strong></h3>
<p>You can install FlyonUI with the command below. Make sure Node.js is installed, then run:</p>
<pre><code class="lang-bash">npm install -D flyonui@latest
</code></pre>
<p>Installs FlyonUI into your project as a development dependency, making its CSS and JS functionality available for integration.</p>
<h3 id="heading-2-add-flyonui-as-plugin-in-the-inputcss">2. Add FlyonUI as plugin in the <code>input.css</code>:</h3>
<pre><code class="lang-css"><span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss"</span>;
<span class="hljs-keyword">@plugin</span> <span class="hljs-string">"flyonui"</span>;
<span class="hljs-keyword">@import</span> <span class="hljs-string">"./node_modules/flyonui/variants.css"</span>; <span class="hljs-comment">/* Needed for JS components */</span>
<span class="hljs-keyword">@source</span> <span class="hljs-string">"./node_modules/flyonui/dist/index.js"</span>; <span class="hljs-comment">/* Needed for JS components */</span>
</code></pre>
<ul>
<li><p><code>@plugin "flyonui"</code> injects FlyonUI’s semantic classes into your build.</p>
</li>
<li><p>The <code>@import</code> includes custom variants created for the JS Components.</p>
</li>
<li><p>The <code>@source</code> line is required for the classes used in the js gets generated.</p>
</li>
</ul>
<h3 id="heading-3-include-flyonui-javascript-for-interactivity">3. <strong>Include FlyonUI JavaScript for Interactivity</strong></h3>
<p>Right before closing the <code>&lt;/body&gt;</code> tag in your HTML, include:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"../node_modules/flyonui/flyonui.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>This script enables the interactive behaviors for FlyonUI components, such as overlays, modals, and dropdowns.</p>
<h3 id="heading-4-use-a-flyonui-component">4. <strong>Use a FlyonUI Component</strong></h3>
<p>For example, update your UI with:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-center justify-center h-screen bg-gray-100"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>
    Hello FlyonUI
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754374843709/41f9d3e3-d0be-4a32-a569-ce0618bea0e9.png" alt="41f9d3e3-d0be-4a32-a569-ce0618bea0e9" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<ul>
<li>The <code>.btn</code> and <code>.btn-primary</code> classes come from FlyonUI—giving you styled, semantic components without crafting custom CSS.</li>
</ul>
<h3 id="heading-why-it-matters"><strong>Why It Matters</strong></h3>
<ul>
<li><p><strong>Cleaner Code</strong>: FlyonUI’s semantic classes make your templates more readable and maintainable compared to verbose utility classes.</p>
</li>
<li><p><strong>Interactive Without the Overhead</strong>: Easily add dynamic features like modals or accordions through FlyonUI’s JS plugins—no need to code them from scratch.</p>
</li>
<li><p><strong>Effortless Styling</strong>: FlyonUI builds on Tailwind’s utility approach, so you can customize components with familiar classes instantly.</p>
</li>
</ul>
<h2 id="heading-lets-test-flyonui-js-components">Let’s Test FlyonUI JS Components</h2>
<p>To show how FlyonUI works, we’ll test one of its JavaScript-powered UI components, the <strong>Modal</strong>. Modals are common UI elements that grab user attention for alerts, confirmations, or extra information without moving away from the current page.</p>
<h3 id="heading-why-test-the-modal">Why Test the Modal?</h3>
<p>Testing the modal helps you:</p>
<ul>
<li><p>Check that FlyonUI’s JavaScript components load and work properly within your Electron and Tailwind setup.</p>
</li>
<li><p>See how simple it is to add interactive features using FlyonUI’s built-in classes and data attributes.</p>
</li>
<li><p>Understand how the modal responds to different screen sizes and how the UI reacts to user actions like opening and closing dialogs.</p>
</li>
</ul>
<h3 id="heading-how-to-test-the-modal">How to Test the Modal</h3>
<p>Copy the following example code into your <code>index.html</code> file. This button will open a modal dialog with some placeholder content:</p>
<p>We’ll use this <a target="_blank" href="https://flyonui.com/docs/overlays/modal/"><strong>Modal example</strong></a> for testing. Copy the following code in your <code>index.html</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span> <span class="hljs-attr">aria-haspopup</span>=<span class="hljs-string">"dialog"</span> <span class="hljs-attr">aria-expanded</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">aria-controls</span>=<span class="hljs-string">"basic-modal"</span> <span class="hljs-attr">data-overlay</span>=<span class="hljs-string">"#basic-modal"</span>&gt;</span>Open modal<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"basic-modal"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"overlay modal overlay-open:opacity-100 hidden overlay-open:duration-300"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"dialog"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"-1"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-dialog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-content"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-title"</span>&gt;</span>Dialog Title<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-text btn-circle btn-sm absolute end-3 top-3"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Close"</span> <span class="hljs-attr">data-overlay</span>=<span class="hljs-string">"#basic-modal"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon-[tabler--x] size-4"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-body"</span>&gt;</span>
        This is some placeholder content to show the scrolling behavior for modals. Instead of repeating the text in the
        modal, we use an inline style to set a minimum height, thereby extending the length of the overall modal and
        demonstrating the overflow scrolling. When content becomes longer than the height of the viewport, scrolling
        will move the modal as needed.
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal-footer"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-soft btn-secondary"</span> <span class="hljs-attr">data-overlay</span>=<span class="hljs-string">"#basic-modal"</span>&gt;</span>Close<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Save changes<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>After updating the file, restart your Electron app:</p>
<pre><code class="lang-html">npm start
</code></pre>
<p>Here’s the result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754374899394/4f153f2b-ca41-495f-b27e-18a2424a1952.png" alt="final result" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You can use Tailwind CSS and Electron to build desktop applications that look good and operate well on different devices. This adds to Electron's many functions and Tailwind's good styling system, letting you stay productive and utilize clean design methods.</p>
<p>The full code and more details are in the repository here: <a target="_blank" href="https://github.com/themeselection/ts-electron-tailwind">ts-electron-tailwindcss</a>.</p>
<p>I have written this tutorial with the help of <a target="_blank" href="https://github.com/PruthviPraj00">Pruthvi Prajapati</a>, an experienced Tailwind CSS developer.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Telehealth App Using Stream Video and Chat SDK in React ]]>
                </title>
                <description>
                    <![CDATA[ Remember when the COVID-19 pandemic moved everything online – doctor’s visits included – and staying home became the safest option?  That moment kicked off a massive shift in how healthcare gets delivered.  Telehealth became more than a workaround. I... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-telehealth-app-using-stream-video-and-chat-sdk-in-react/</link>
                <guid isPermaLink="false">687ad06430967c1524c4663e</guid>
                
                    <category>
                        <![CDATA[ streams SDK ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Streams ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MERN Stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mern Stack Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Okoro Emmanuel Nzube ]]>
                </dc:creator>
                <pubDate>Fri, 18 Jul 2025 22:53:24 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752879185676/ab0574c8-d16b-41d0-8883-7df0f4bd0eb5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Remember when the COVID-19 pandemic moved everything online – doctor’s visits included – and staying home became the safest option? </p>
<p>That moment kicked off a massive shift in how healthcare gets delivered. </p>
<p><a target="_blank" href="https://getstream.io/chat/solutions/healthcare/">Telehealth</a> became more than a workaround. It’s now a core part of modern care. As demand grows, developers are stepping up to build secure, real-time platforms that connect patients and providers from anywhere.</p>
<p>In this article, you’ll learn how to build a telehealth application with Stream’s <a target="_blank" href="https://getstream.io/video/sdk/react/">React Video</a> and <a target="_blank" href="https://getstream.io/chat/sdk/react/">Chat</a> SDKs. You’ll set up authentication, create video calls, enable messaging, and design a functional user interface that mimics real-world telehealth workflows.</p>
<p>Let’s dive in.</p>
<h2 id="heading-outline">Outline</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-introduction">Introduction</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-app-structure">App Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-backend-setup">Backend Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-setup">Frontend Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-stream-chat-and-video-integration">Stream Chat and Video Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-chat-and-video-function-\(frontend\)">Chat and Video Function (Frontend)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-demo">Project Demo</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-Conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start this tutorial, make sure you have:</p>
<ul>
<li><p>A basic understanding of React.</p>
</li>
<li><p>Node.js and npm/yarn installed on your computer</p>
</li>
<li><p>A <a target="_blank" href="https://getstream.io/try-for-free/">free account with Stream</a></p>
</li>
<li><p>Familiarity with Stream SDKs</p>
</li>
<li><p>A basic understanding of Tailwind CSS for styling</p>
</li>
<li><p>Experience with VS Code and Postman (for testing APIs)</p>
</li>
</ul>
<h2 id="heading-app-structure"><strong>App Structure</strong></h2>
<p>Before diving into the code, it’s helpful to understand how the app is structured.</p>
<pre><code class="lang-markdown"><span class="hljs-section"># App Flow Structure</span>

<span class="hljs-bullet">-</span> Landing Page  
<span class="hljs-bullet">  -</span> Navigation  
<span class="hljs-bullet">    -</span> Home  
<span class="hljs-bullet">    -</span> About  
<span class="hljs-bullet">    -</span> Sign Up  
<span class="hljs-bullet">      -</span> Verify Account  
<span class="hljs-bullet">        -</span> Log In  
<span class="hljs-bullet">          -</span> Dashboard  
<span class="hljs-bullet">            -</span> Stream Chat  
<span class="hljs-bullet">            -</span> Stream Video  
<span class="hljs-bullet">            -</span> Log Out
</code></pre>
<h2 id="heading-project-setup"><strong>Project Setup</strong></h2>
<p>Before getting started, create two folders: “Frontend” to handle the client-side code and “Backend” for the server-side logic. This separation allows you to manage both parts of your application efficiently.</p>
<h3 id="heading-set-up-react-for-the-frontend"><strong>Set Up React for the Frontend</strong></h3>
<p>Once the folders are created, you can set up the React application in the Frontend folder.</p>
<p>First, navigate to the Frontend directory using the command <code>cd Frontend</code>.</p>
<p>Now you can initialize your React project. You’ll use Vite, a fast build tool for React applications.</p>
<p>To create your React project, run the following command:</p>
<p><code>npm create vite@latest [project name] -- --template react</code></p>
<p> Next, navigate to your new project folder, using the command:</p>
<p><code>cd [project name]</code></p>
<p>Once there, install the required dependencies by running:</p>
<p><code>npm install</code></p>
<p>This command installs both the <code>node_modules</code> folder (which contains all your project's packages) and the <code>package-lock.json</code> file (which records exact versions of installed packages).</p>
<p>Next, you’ll need to install Tailwind CSS for styling. Follow the <a target="_blank" href="https://v3.tailwindcss.com/docs/guides/vite">Tailwind Docs</a> for step-by-step instructions.</p>
<p>Then, it’s time to set up the website. Using React, you’ll create the home sign-in/log-in pages. Both will be nested together using <code>React-router-dom</code>.</p>
<p>Here’s what the <a target="_blank" href="https://github.com/Derekvibe/Telehealth_Frontend/tree/main/src/pages/Home">home page</a> looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752705718347/ed5dd289-2998-41f7-8d10-352aa35fe614.gif" alt="Telehealth Home Page" class="image--center mx-auto" width="1415" height="666" loading="lazy"></p>
<p>Now, the user has a place to land whenever they visit the website.</p>
<p>Let’s set up the backend.</p>
<h2 id="heading-backend-setup"><strong>Backend Setup</strong></h2>
<h3 id="heading-installing-required-packages"><strong>Installing Required Packages</strong></h3>
<p>Before setting up your project's backend, it's important to define what your project needs to offer. This will help you install all the necessary packages in one go.</p>
<p>Start by moving into the backend folder using the command: <code>cd Backend</code></p>
<p>Inside the Backend directory, initialize your Node.js project using <code>npm install</code></p>
<p>This will create a <code>package.json</code> file, which stores metadata and dependencies for your project.</p>
<p>Next, install all the dependencies needed to build your backend. Run the following command:</p>
<p><code>npm i bcryptjs cookie-parser cors dotenv express jsonwebtoken mongoose nodemailer validator nodemon</code></p>
<p>Here’s a brief overview of what each package does:</p>
<ul>
<li><p>bcryptjs: Encrypts user passwords for secure storage.</p>
</li>
<li><p>Cookie-parser: Handles cookies in your application.</p>
</li>
<li><p>CORS: Middleware that enables cross-origin requests – essential for frontend-backend communication.</p>
</li>
<li><p>dotenv: Loads environment variables from a <code>.env</code> file into process.env.</p>
</li>
<li><p>Express: The core framework for building your server and API routes.</p>
</li>
<li><p>jsonwebtoken: Generates and verifies JWT tokens for authentication.</p>
</li>
<li><p>Mongoose: Connects your app to a MongoDB database.</p>
</li>
<li><p>nodemailer: Handles sending emails from your application.</p>
</li>
<li><p>Validator: Validates user inputs like email, strings, and so on.</p>
</li>
<li><p>nodemon: Automatically restarts your server when changes are made to files.</p>
</li>
</ul>
<p>Once your packages are installed, create two key files in the backend directory: <code>App.js</code>, which contains your app logic, middleware, and route handlers, and <code>server.js</code>, responsible for initializing and configuring your server.</p>
<p>Next, you have to update your <code>package.json</code> start script. Head to the <code>package.json</code> file in your backend directory and replace the default script:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo\”Error: no test specified\" &amp;&amp; exit 1”
  }</span>
</code></pre>
<p>with this:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"nodemon server.js"</span>
  }
</code></pre>
<p>This setup allows you to run your server using <code>nodemon</code>, automatically reloading it when changes are made. This helps boost productivity during development.</p>
<p>To check if your backend setup is correct, open the <code>server.js</code> file and add a test log: <code>console.log (“Any of your Log Message”)</code>. Then, head to your terminal in the backend directory, and run npm start. You should see the log message in the terminal, confirming that your backend is running.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752703046663/dc06ce5a-3b6c-4846-bd33-53c423a57235.png" alt="Backend Server Testing" class="image--center mx-auto" width="1120" height="986" loading="lazy"></p>
<h3 id="heading-appjs-setup"><code>App.js</code> <strong>Setup</strong></h3>
<p>In the <code>App.js</code> file, start by importing the packages you initially installed.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);

<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);

<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cookie-parser"</span>);

<span class="hljs-keyword">const</span> app = express();


app.use(

  cors({

<span class="hljs-attr">origin</span>: [

   <span class="hljs-string">"http://localhost:5173"</span>,

],

<span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>,

  })

);

app.use(express.json({ <span class="hljs-attr">limit</span>: <span class="hljs-string">"10kb"</span> }));

app.use(cookieParser());

<span class="hljs-built_in">module</span>.exports = app;
</code></pre>
<p>Here’s what the code above does: </p>
<p>The require statements import <code>express</code>, <code>cors</code>, and <code>cookie-parser</code>, which are essential for creating your backend server, handling cross-origin requests, and parsing cookies.</p>
<p>The <code>const app = express();</code> command sets up a new instance of an Express application. </p>
<p><code>app.use(cors({ origin: ["</code><a target="_blank" href="http://localhost:5173"><code>http://localhost:5173</code></a><code>"], credentials: true }));</code> grants permission or allows requests from your frontend and enables cookie sharing between the frontend and backend of your application. This is important for authentication.</p>
<p>The <code>app.use(express.json({ limit: "10kb" }));</code> command is a middleware function that ensures the server can process incoming JSON payloads and protects against overly large requests, which could be used in DoS attacks. </p>
<p>The <code>app.use(cookieParser());</code> command makes cookies available via <code>req.cookies</code>.</p>
<p>Last, the <code>module.exports = app;</code> command allows the app to be imported in other files, especially in <code>server.js</code>, which is where the app will be started.</p>
<h3 id="heading-serverjs-setup"><code>Server.js</code> <strong>Setup</strong></h3>
<p>Once <code>App.js</code> is set up, the next step is to create and configure your server in a new file called <code>server.js</code>.</p>
<p>Before doing that, ensure you have a <strong>MongoDB database</strong> set up. If you don’t have one yet, you can <a target="_blank" href="https://youtu.be/pO6m0nmo1k0?si=Rqi_50fnsfQrM-ww">follow this video tutorial</a> to set up a MongoDB database.</p>
<p>After setting up MongoDB, you will receive a <code>username</code> and <code>password</code>. Copy the password, head to your backend directory, and create a <code>.env</code> file to store it.</p>
<p>After you have stored the password, head back to complete your database setup.</p>
<p>Next, click on the “Create Database User” button, then click on the <code>choose connection method</code> option. Since we are using Node.js for this project, choose the “Drivers” option. This gives you the database connection string (you should see it at No. 3).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752706253668/ad0cdbb4-453c-4291-ab4c-395d14ce297c.gif" alt="Database-String-Auth" class="image--center mx-auto" width="1280" height="671" loading="lazy"></p>
<p>Then head to your <code>.env</code> and paste it there, and add <code>auth</code> immediately after you have “.net/”.</p>
<p>Here’s what it looks like:</p>
<p><code>mongodb+srv://&lt;username&gt;:&lt;password&gt;@cluster0.qrrtmhs.mongodb.net/auth?retryWrites=true&amp;w=majority&amp;appName=Cluster0</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752767020758/aee4f54c-e562-4916-a8f5-b97590a671d1.png" alt="Backend config.env file" class="image--center mx-auto" width="1632" height="426" loading="lazy"></p>
<p>Lastly, whitelist your IP address. This ensures your backend can connect to MongoDB from your local machine or any environment during development.</p>
<p>To allow your application to connect to the database:</p>
<ul>
<li><p>Go to the "Network Access" section in the Security sidebar of your MongoDB dashboard.</p>
</li>
<li><p>Click on “ADD IP ADDRESS.”</p>
</li>
<li><p>Choose “Allow Access from Anywhere”, then click on Confirm.</p>
</li>
</ul>
<p>At this point, you can set up your <code>server.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//server.js</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>); <span class="hljs-comment">//to Manage our environment variable</span>

dotenv.config({ <span class="hljs-attr">path</span>: <span class="hljs-string">"./config.env"</span> });
<span class="hljs-comment">// console.log(process.env.NODE_ENV);</span>

<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./app"</span>);

<span class="hljs-keyword">const</span> db = process.env.DB;
<span class="hljs-comment">//connect the application to database using MongoDB</span>

mongoose
  .connect(db)
  .then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"DB connection Successful"</span>);
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(err);
  });

<span class="hljs-keyword">const</span> port = process.env.PORT || <span class="hljs-number">3000</span>;
<span class="hljs-comment">// console.log(process.env.PORT)</span>

app.listen(port, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`App running on port <span class="hljs-subst">${port}</span>`</span>);
});
</code></pre>
<p>The <code>server.js</code> file is responsible for handling all server-related functions and logic. From the code above, the <code>server.js</code> file loads the environment variables using <code>dotenv</code>, connects your backend to MongoDB using <code>mongoose</code>, and starts the Express server. It gets the database URL and port from the <code>config.env</code> file, connects to the database, then runs your application on the specified port (<code>8000</code>).</p>
<p>If the specified port is not found, it falls back to port <code>3000</code> and a confirmation message is printed to the console indicating that the server is up and running on the specified port.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752703203108/a94c724c-ad9c-4653-9081-894ac6e44dd6.png" alt="server-js Telehealth App" class="image--center mx-auto" width="991" height="800" loading="lazy"></p>
<h3 id="heading-connect-the-database-to-mongodb-compass">Connect the Database to MongoDB Compass</h3>
<p>First, download the MongoDB Compass app. (Go here to download and install: <a target="_blank" href="https://www.mongodb.com/try/download/compass">https://www.mongodb.com/try/download/compass</a>). The MongoDB Compass app makes it easy for us to manage our data.</p>
<p>Once the installation is complete, open the app and click on <code>Click to add new connection</code>. Go to your <code>.env</code> file, copy the connection string you initially got when setting up MongoDB, paste it in the URL section, and then click on “connect.” This setup helps you manage your data when you create and delete users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752703344533/8dff0ff6-66e9-4359-a2c0-fe7a4bd5e4ba.png" alt="Mongo-DB-Compass" class="image--center mx-auto" width="1263" height="790" loading="lazy"></p>
<h3 id="heading-set-up-an-advanced-error-handling-method"><strong>Set up an Advanced Error Handling Method</strong></h3>
<p>You’ll now create an advanced error-handling mechanism. To do so, create a utils folder in your backend, a <code>catchAsync.js</code> file in the utils folder, and add this code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//catchAsync.js</span>
<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">fn</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
    fn(req, res, next).catch(next);
  };
};
</code></pre>
<p>Next, create an <code>appError.js</code> file still in your utils folder. In the <code>appError.js</code> file, add the following command:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppError</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Error</span> </span>{
  <span class="hljs-keyword">constructor</span>(message, statusCode) {
    <span class="hljs-built_in">super</span>(message);

    <span class="hljs-built_in">this</span>.statusCode = statusCode;
    <span class="hljs-built_in">this</span>.status = <span class="hljs-string">`<span class="hljs-subst">${statusCode}</span>`</span>.startsWith(<span class="hljs-string">"4"</span>) ? <span class="hljs-string">"fail"</span> : <span class="hljs-string">"error"</span>;
    <span class="hljs-built_in">this</span>.isOperational = <span class="hljs-literal">true</span>;

    <span class="hljs-built_in">Error</span>.captureStackTrace(<span class="hljs-built_in">this</span>, <span class="hljs-built_in">this</span>.constructor);
  }
}

<span class="hljs-built_in">module</span>.exports = AppError;
</code></pre>
<p>The code above is helpful in tracking and tracing errors. It also provides you with the URL and file location where your error might be occurring, which helps with cleaner error handling and debugging.</p>
<p>Next, let’s create a global error handler. Start by creating a new folder in the backend directory, and name it “controller”. In the controller folder, create your global error handling file. You can name it anything you like. In this example, it’s called <code>globalErrorHandler.js</code>.</p>
<p>Your <code>globalErrorHandler</code> file will define several functions that handle specific error types, such as database issues, validation failures, or even JWT problems and return a nicely formatted error response for users. For the <code>globalErrorHandler</code> to work properly, you have to create a controller function.</p>
<p>So, next, create an <code>errorController.js</code> file (still inside the controller folder). The <code>errorController.js</code> file responds to the user whenever an error is caught, sending the error in JSON format.</p>
<p><code>globalErrorHandler.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// globalErrorHandler.js</span>
<span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);

<span class="hljs-keyword">const</span> handleCastErrorDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Invalid <span class="hljs-subst">${err.path}</span>: <span class="hljs-subst">${err.value}</span>.`</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
};

<span class="hljs-keyword">const</span> handleDuplicateFieldsDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> value = err.keyValue ? <span class="hljs-built_in">JSON</span>.stringify(err.keyValue) : <span class="hljs-string">"duplicate field"</span>;
  <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Duplicate field value: <span class="hljs-subst">${value}</span>. Please use another value!`</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
};

<span class="hljs-keyword">const</span> handleValidationErrorDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> errors = <span class="hljs-built_in">Object</span>.values(err.errors).map(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> el.message);
  <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Invalid input: <span class="hljs-subst">${errors.join(<span class="hljs-string">". "</span>)}</span>`</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
};

<span class="hljs-keyword">const</span> handleJWTError = <span class="hljs-function">() =&gt;</span>
  <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid token. Please log in again!"</span>, <span class="hljs-number">401</span>);
<span class="hljs-keyword">const</span> handleJWTExpiredError = <span class="hljs-function">() =&gt;</span>
  <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Your token has expired! Please log in again."</span>, <span class="hljs-number">401</span>);

<span class="hljs-built_in">module</span>.exports = {
  handleCastErrorDB,
  handleDuplicateFieldsDB,
  handleValidationErrorDB,
  handleJWTError,
  handleJWTExpiredError,
};
</code></pre>
<p>Here’s what the code above does:</p>
<p>The <code>const handleCastErrorDB = (err) =&gt;..</code> section handles MongoDB CastError which usually happens when an invalid ID is passed, and returns a <code>400 Bad Request</code> error response using your <code>AppError</code> class.</p>
<p>The <code>const handleDuplicateFieldsDB = (err) =&gt;...</code> checks and handles MongoDB duplicate key errors, such as trying to register an email or username that’s already taken and returns a <code>400 Bad Request</code> error.</p>
<p>The <code>const handleValidationErrorDB = (err) =&gt;...</code> handles Mongoose validation errors (like required fields missing or wrong data types). It gathers all the individual validation error messages and combines them into one.</p>
<p>The <code>const handleJWTError = () =&gt;</code> and <code>const handleJWTExpiredError = () =&gt;</code> handle errors which might occur as a result of invalid, tampered, or expired JWT tokens and return a <code>401 Unauthorized</code> error response.</p>
<p>The <code>module.exports = {……};</code> section exports all the individual error handlers so they can be used in the <code>errorController.js</code> file.</p>
<p><code>errorController.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//errorController.js</span>
<span class="hljs-keyword">const</span> errorHandlers = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./globalErrorHandler"</span>);

<span class="hljs-keyword">const</span> {
  handleCastErrorDB,
  handleDuplicateFieldsDB,
  handleValidationErrorDB,
  handleJWTError,
  handleJWTExpiredError,
} = errorHandlers;

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> {
  err.statusCode = err.statusCode || <span class="hljs-number">500</span>;
  err.status = err.status || <span class="hljs-string">"error"</span>;

  <span class="hljs-keyword">let</span> error = { ...err, <span class="hljs-attr">message</span>: err.message };

  <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"CastError"</span>) error = handleCastErrorDB(err);
  <span class="hljs-keyword">if</span> (err.code === <span class="hljs-number">11000</span>) error = handleDuplicateFieldsDB(err);
  <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"ValidationError"</span>) error = handleValidationErrorDB(err);
  <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"JsonWebTokenError"</span>) error = handleJWTError();
  <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"TokenExpiredError"</span>) error = handleJWTExpiredError();

  res.status(error.statusCode).json({
    <span class="hljs-attr">status</span>: error.status,
    <span class="hljs-attr">message</span>: error.message,
    ...(process.env.NODE_ENV === <span class="hljs-string">"production"</span> &amp;&amp; { error, <span class="hljs-attr">stack</span>: err.stack }),
  });
};
</code></pre>
<p>To ensure your error-handling function works properly, head to your <code>App.js</code> and add the command:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//import command</span>
<span class="hljs-keyword">const</span> globalErrorHandler = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./controller/errorController"</span>);
<span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./utils/appError"</span>);

<span class="hljs-comment">//Catch unknown routes</span>
app.all(<span class="hljs-string">"/{*any}"</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">`Can't find <span class="hljs-subst">${req.originalUrl}</span> on this server!`</span>, <span class="hljs-number">404</span>)); });

app.use(globalErrorHandler);
</code></pre>
<p>This ensures that all errors are properly handled and sends the error response to the user.</p>
<h3 id="heading-create-user-model"><strong>Create User Model</strong></h3>
<p>To build a user model, create a new folder in the backend directory and name it “model.” Inside the model folder, create a new file named <code>userModel.js</code>.</p>
<p>The <code>userModel.js</code> file is built essentially for user authentication and security. It provides a validation-rich schema for managing users using Mongoose, which maps how user data is structured in the MongoDB database. It includes validations, password hashing, and methods to compare user passwords securely.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//userModel.js</span>
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> validator = <span class="hljs-built_in">require</span>(<span class="hljs-string">"validator"</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"bcryptjs"</span>);

<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema(
  {
    <span class="hljs-attr">username</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please provide username"</span>], <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">minlength</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">maxlength</span>: <span class="hljs-number">30</span>, <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>,},
    <span class="hljs-attr">email</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please Provide an email"</span>], <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">lowercase</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">validate</span>: [validator.isEmail, <span class="hljs-string">"Please provide a valid email"</span>],},
    <span class="hljs-attr">password</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please provide a Password"</span>], <span class="hljs-attr">minlength</span>: <span class="hljs-number">8</span>, <span class="hljs-attr">select</span>: <span class="hljs-literal">false</span>,},
    <span class="hljs-attr">passwordConfirm</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please confirm your Password"</span>],
     <span class="hljs-attr">validate</span>: {<span class="hljs-attr">validator</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">el</span>) </span>{<span class="hljs-keyword">return</span> el === <span class="hljs-built_in">this</span>.password;},
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Passwords do not match"</span>,
      },
    },
    <span class="hljs-attr">isVerified</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>, <span class="hljs-attr">default</span>: <span class="hljs-literal">false</span>,}, <span class="hljs-attr">otp</span>: <span class="hljs-built_in">String</span>,
    <span class="hljs-attr">otpExpires</span>: <span class="hljs-built_in">Date</span>,
     <span class="hljs-attr">resetPasswordOTP</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">resetPasswordOTPExpires</span>: <span class="hljs-built_in">Date</span>,
    <span class="hljs-attr">createdAt</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now,},}, { <span class="hljs-attr">timestamps</span>: <span class="hljs-literal">true</span> });

<span class="hljs-comment">// Hash password before saving</span>
userSchema.pre(<span class="hljs-string">"save"</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">next</span>) </span>{
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isModified(<span class="hljs-string">"password"</span>)) <span class="hljs-keyword">return</span> next();

  <span class="hljs-built_in">this</span>.password = <span class="hljs-keyword">await</span> bcrypt.hash(<span class="hljs-built_in">this</span>.password, <span class="hljs-number">12</span>);
  <span class="hljs-built_in">this</span>.passwordConfirm = <span class="hljs-literal">undefined</span>; <span class="hljs-comment">// Remove passwordConfirm before saving</span>
  next();
});

<span class="hljs-keyword">const</span> User = mongoose.model(<span class="hljs-string">"User"</span>, userSchema);
<span class="hljs-built_in">module</span>.exports = User;
</code></pre>
<h3 id="heading-auth-controller"><strong>Auth Controller</strong></h3>
<p>Now, you can create logic that regulates your user's authentication process. This authentication logic consists of the sign-up, sign-in (log-in), OTP, and so on. </p>
<p>To do so, first head to your controller folder and create a new file. Call it <code>authController.js</code> because it handles the authentication flow of your project.</p>
<p>After you’ve created the file, you’ll create your sign-up function.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//import</span>
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../model/userModel"</span>);
<span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);
<span class="hljs-keyword">const</span> catchAsync = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/catchAsync"</span>);
<span class="hljs-keyword">const</span> generateOtp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/generateOtp"</span>);
<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
<span class="hljs-keyword">const</span> sendEmail = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/email"</span>)

<span class="hljs-built_in">exports</span>.signup = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  <span class="hljs-keyword">const</span> { email, password, passwordConfirm, username } = req.body;

  <span class="hljs-keyword">const</span> existingUser = <span class="hljs-keyword">await</span> User.findOne({ email });

  <span class="hljs-keyword">if</span> (existingUser) <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Email already registered"</span>, <span class="hljs-number">400</span>));

  <span class="hljs-keyword">const</span> otp = generateOtp();

  <span class="hljs-keyword">const</span> otpExpires = <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">//when thhe otp will expire (1 day)</span>

  <span class="hljs-keyword">const</span> newUser = <span class="hljs-keyword">await</span> User.create({
    username,
    email,
    password,
    passwordConfirm,
    otp,
    otpExpires,
  });

  <span class="hljs-comment">//configure email sending functionality</span>
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> sendEmail({
      <span class="hljs-attr">email</span>: newUser.email,
      <span class="hljs-attr">subject</span>: <span class="hljs-string">"OTP for email Verification"</span>,
      <span class="hljs-attr">html</span>: <span class="hljs-string">`&lt;h1&gt;Your OTP is : <span class="hljs-subst">${otp}</span>&lt;/h1&gt;`</span>,
    });

    createSendToken(newUser, <span class="hljs-number">200</span>, res, <span class="hljs-string">"Registration successful"</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Email send error:"</span>, error);
    <span class="hljs-keyword">await</span> User.findByIdAndDelete(newUser.id);
    <span class="hljs-keyword">return</span> next(
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"There is an error sending the email. Try again"</span>, <span class="hljs-number">500</span>)
    );
  }
});
</code></pre>
<p><code>const { email, password, passwordConfirm, username } = req.body;</code> extracts the necessary registration details from the incoming request: email, password, password confirmation, and username during user sign-up.</p>
<p><code>const existingUser = await User.findOne({ email });</code> checks the database to see if a user already exists with the given email. If yes, it sends an error response using your <code>AppError</code> utility.</p>
<p><code>const otp = generateOtp();</code> generates the OTP, and <code>const otpExpires =</code> <a target="_blank" href="http://date.now"><code>Date.now</code></a><code>()…..</code> is used to set the OTP to expire at a specified time or day.</p>
<p>With <code>const newUser = await User.create({…});</code>, the new user is saved in MongoDB with their credentials and the OTP info, with the password automatically hashed.</p>
<p><code>await sendEmail({…});</code> sends an email to the user. This email contains their sign-in OTP. If the email is sent successfully, <code>createSendToken(newUser, 200, res, "Registration successful");</code> (which is a utility function) generates a JWT token and sends it in the response with a success message.</p>
<p>If the email fails to send or something goes wrong, <code>await User.findByIdAndDelete(</code><a target="_blank" href="http://newuser.id"><code>newUser.id</code></a><code>);</code> deletes the user from the database to keep things clean, and an error message of <code>There is an error sending the email. Try again", 500</code> is returned.</p>
<h3 id="heading-generate-otp"><strong>Generate OTP</strong></h3>
<p>To ensure that your users' OTP is successfully sent to them, in the utils folder, create a new file and name it <code>generateOtp.js</code>. Then add the code:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.floor(<span class="hljs-number">1000</span> + <span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">9000</span>).toString();
};
</code></pre>
<p>The code above is a function that generates a user's random 4-digit OTP and returns it as a string.</p>
<p>After completing the code above, go to your authController.js and ensure you import the <code>generateOtp.js</code> in the import section.</p>
<h3 id="heading-create-user-token"><strong>Create User Token</strong></h3>
<p>Next, the user sign-in token will be created, and it will be assigned to the user upon sign-in.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> signToken = <span class="hljs-function">(<span class="hljs-params">userId</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> jwt.sign({ <span class="hljs-attr">id</span>: userId }, process.env.JWT_SECRET, {
    <span class="hljs-attr">expiresIn</span>: process.env.JWT_EXPIRES_IN || <span class="hljs-string">"90d"</span>,
  });
};

<span class="hljs-comment">//function to create the token</span>
<span class="hljs-keyword">const</span> createSendToken = <span class="hljs-function">(<span class="hljs-params">user, statusCode, res, message</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> token = signToken(user._id);

  <span class="hljs-comment">//function to generate the cookie</span>
  <span class="hljs-keyword">const</span> cookieOptions = {
    <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
      <span class="hljs-built_in">Date</span>.now() + process.env.JWT_COOKIE_EXPIRES_IN <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> <span class="hljs-number">1000</span>
    ),

    <span class="hljs-attr">httponly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>, <span class="hljs-comment">//only secure in production</span>
    <span class="hljs-attr">sameSite</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span> ? <span class="hljs-string">"none"</span> : <span class="hljs-string">"Lax"</span>,
  };

  res.cookie(<span class="hljs-string">"token"</span>, token, cookieOptions);

  user.password = <span class="hljs-literal">undefined</span>;
  user.passwordConfirm = <span class="hljs-literal">undefined</span>;
  user.otp = <span class="hljs-literal">undefined</span>;
</code></pre>
<p>Before the code above can work perfectly, create a JWT in your <code>.env</code> file.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//.env</span>
JWT_SECRET = kaklsdolrnnhjfsnlsoijfbwhjsioennbandksd;
JWT_EXPIRES_IN = <span class="hljs-number">90</span>d
JWT_COOKIE_EXPIRES_IN = <span class="hljs-number">90</span>
</code></pre>
<p>The code above is how the <code>.env</code> file should look. Your <code>JWT_SECRET</code> can be anything, just as you can see in the code.</p>
<p>Note: The user token creation logic should run before the sign-in logic. So in that case, the <code>signToken</code> and <code>createSendToken</code> logic should be placed at the top before the <code>signup</code> logic.</p>
<h3 id="heading-send-email"><strong>Send Email</strong></h3>
<p>Next, you need to configure your email sending functionality so you can automatically send the user's OTP to their email whenever they sign in. To configure the email, head to the utils folder, create a new file, and give it a name. In this example, the name is <code>email.js</code>.</p>
<p>In <code>email.js,</code> we will send emails using the <code>nodemailer</code> package and Gmail as a provider.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//email.js</span>
<span class="hljs-keyword">const</span> nodemailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'nodemailer'</span>);

<span class="hljs-keyword">const</span> sendEmail = <span class="hljs-keyword">async</span> (options) =&gt; {
  <span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
    <span class="hljs-attr">service</span>: <span class="hljs-string">'Gmail'</span>,
    <span class="hljs-attr">auth</span>: {
      <span class="hljs-attr">user</span>: process.env.HOST_EMAIL,
      <span class="hljs-attr">pass</span>: process.env.EMAIL_PASS
    }
  })

  <span class="hljs-comment">//defining email option and structure</span>

  <span class="hljs-keyword">const</span> mailOptions = {
    <span class="hljs-attr">from</span>: <span class="hljs-string">`"{HOST Name}" &lt;{HOST Email} &gt;`</span>,
    <span class="hljs-attr">to</span>: options.email,
    <span class="hljs-attr">subject</span>: options.subject,
    <span class="hljs-attr">html</span>: options.html,
  };
  <span class="hljs-keyword">await</span> transporter.sendMail(mailOptions);
};

<span class="hljs-built_in">module</span>.exports = sendEmail;
</code></pre>
<p>From the code above, the <code>const nodemailer = require('nodemailer');</code> command imports the <code>nodemailer</code> package. This is a popular Node.js library for sending emails.</p>
<p>The <code>const transporter = nodemailer.createTransport({…..})</code> is an email transporter. Since we will be using the Gmail service provider, <code>service</code> will be assigned to <code>Gmail</code> and <code>auth</code> pulls your Gmail address and password from the <code>.env</code> file where it’s stored.</p>
<p>Note: The password is not your actual Gmail password but rather your Gmail app password. You can see how you can get your <a target="_blank" href="https://youtu.be/MkLX85XU5rU?si=yBIj4MJDLY7-k-c4">Gmail password here</a>.</p>
<p>Once you’ve successfully gotten your Gmail app password, store it in your <code>.env</code> file.</p>
<h3 id="heading-route-creation"><strong>Route Creation</strong></h3>
<p>At this point, you have finished setting up your project's signup function. Next, you need to test whether your signup works properly using Postman. But before that, let’s set up and define a route where the signup function will be executed.</p>
<p>To set up your route, create a new folder in your backend directory named "routes" and a file named <code>userRouter.js</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> {signup} = <span class="hljs-built_in">require</span>(“../controller/authController”);

<span class="hljs-keyword">const</span> router = express.Router();
router.post(<span class="hljs-string">"/signup"</span>, signup);

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Next, go to your <code>App.js</code> file and add the router to it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userRouter = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/userRouters"</span>); <span class="hljs-comment">//Route import statement</span>
app.use(<span class="hljs-string">"/api/v1/users"</span>, userRouter) <span class="hljs-comment">//common route for all auth, i.e sign up, log in, forget password, etc.</span>
</code></pre>
<p>After setting up your routes, you can test your signup to see if it works. This is a post request, and the route URL will be <a target="_blank" href="http://localhost:8000/api/v1/users/signup%60"><code>http://localhost:8000/api/v1/users/signup</code></a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752704061383/380d8480-9997-4678-8ca7-0ed86ea24481.png" alt="New user Sign up Testing" class="image--center mx-auto" width="1280" height="716" loading="lazy"></p>
<p>The image above shows that the signup function works perfectly with a <code>statusCode</code> of <code>200</code> and an OTP code being sent to the user’s email.</p>
<p>Congratulations on reaching this point! You can check your MongoDB database to see if the user is displayed there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752703565468/0f23f8ab-d17e-4555-8347-475bb6483b8a.png" alt="Mongo-DB-Compass-User-Display" class="image--center mx-auto" width="1280" height="495" loading="lazy"></p>
<p>From the image above, you can see that the user details are obtained and the password is in an encrypted form, which ensures the user credentials are safe.</p>
<h3 id="heading-create-a-verify-account-controller-function"><strong>Create a Verify Account Controller Function</strong></h3>
<p>In this section, you’ll create a Verify Account controller function. This function verifies a user’s account using the OTP code sent to their email address.</p>
<p>First, go to your <code>authController.js</code> file and add:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">exports</span>.verifyAccount = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  <span class="hljs-keyword">const</span> { email, otp } = req.body;

  <span class="hljs-keyword">if</span> (!email || !otp) {
    <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Email and OTP are required"</span>, <span class="hljs-number">400</span>));
  }

  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });

  <span class="hljs-keyword">if</span> (!user) {
    <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"No user found with this email"</span>, <span class="hljs-number">404</span>));
  }

  <span class="hljs-keyword">if</span> (user.otp !== otp) {
    <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid OTP"</span>, <span class="hljs-number">400</span>));
  }

  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Date</span>.now() &gt; user.otpExpires) {
    <span class="hljs-keyword">return</span> next(
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"OTP has expired. Please request a new OTP."</span>, <span class="hljs-number">400</span>)
    );
  }

  user.isVerified = <span class="hljs-literal">true</span>;
  user.otp = <span class="hljs-literal">undefined</span>;
  user.otpExpires = <span class="hljs-literal">undefined</span>;

  <span class="hljs-keyword">await</span> user.save({ <span class="hljs-attr">validateBeforeSave</span>: <span class="hljs-literal">false</span> });

  <span class="hljs-comment">// ✅ Optionally return a response without logging in</span>
  res.status(<span class="hljs-number">200</span>).json({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Email has been verified"</span>,
  });
});
</code></pre>
<p>Next, create a middleware function to authenticate the currently logged-in user.</p>
<p>In your backend directory, create a new folder called <code>middlewares</code>. Inside the <code>middlewares</code> folder, create a file named <code>isAuthenticated.js</code>.</p>
<p>Add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//isAuthenticated.js</span>
<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
<span class="hljs-keyword">const</span> catchAsync = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/catchAsync"</span>);
<span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../model/userModel"</span>);

<span class="hljs-keyword">const</span> isAuthenticated = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  <span class="hljs-keyword">let</span> token;

  <span class="hljs-comment">// 1. Retrieve token from cookies or Authorization header</span>
  <span class="hljs-keyword">if</span> (req.cookies?.token) {
    token = req.cookies.token;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (req.headers.authorization?.startsWith(<span class="hljs-string">"Bearer"</span>)) {
    token = req.headers.authorization.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>];
  }

  <span class="hljs-keyword">if</span> (!token) {
    <span class="hljs-keyword">return</span> next(
      <span class="hljs-keyword">new</span> AppError(
        <span class="hljs-string">"You are not logged in. Please log in to access this resource."</span>,
        <span class="hljs-number">401</span>
      )
    );
  }

  <span class="hljs-comment">// 2. Verify token</span>
  <span class="hljs-keyword">let</span> decoded;
  <span class="hljs-keyword">try</span> {
    decoded = jwt.verify(token, process.env.JWT_SECRET);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">return</span> next(
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid or expired token. Please log in again."</span>, <span class="hljs-number">401</span>)
    );
  }

<span class="hljs-comment">// 3. Confirm user still exists in database</span>
  <span class="hljs-keyword">const</span> currentUser = <span class="hljs-keyword">await</span> User.findById(decoded._id);
  <span class="hljs-keyword">if</span> (!currentUser) {
    <span class="hljs-keyword">return</span> next(
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"User linked to this token no longer exists."</span>, <span class="hljs-number">401</span>)
    );
  }

  <span class="hljs-comment">// 4. Attach user info to request</span>
  req.user = currentUser;
  req.user = {
    <span class="hljs-attr">id</span>: currentUser.id,
    <span class="hljs-attr">name</span>: currentUser.name,
  };

  next();
});

<span class="hljs-built_in">module</span>.exports = isAuthenticated;
<span class="hljs-string">``</span><span class="hljs-string">`
Now, go to your `</span>userRouter.js<span class="hljs-string">` file and add the route for the verify account function:
`</span><span class="hljs-string">``</span>
<span class="hljs-keyword">const</span> { verifyAccount} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
<span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);
router.post(<span class="hljs-string">"/verify"</span>, isAuthenticated, verifyAccount);
</code></pre>
<p>Here is what these two sets of code are doing:</p>
<p>When a user sends a request to the <code>/verify</code> route, the <code>isAuthenticated</code> middleware runs first. It checks whether a valid token exists in the cookie or authorization header. If no token is found, it throws an error: <code>You are not logged in. Please log in to access this resource.</code></p>
<p>If a token is found, it verifies the token and checks if the associated user still exists. If not, another error is thrown: <code>"User linked to this token no longer exists."</code></p>
<p>If the user exists and the token is valid, their details are attached to the request (<code>req.user</code>). The request then proceeds to the <code>verifyAccount</code> controller, which handles OTP verification.</p>
<p>You can test this endpoint using Postman with a POST request to: <a target="_blank" href="http://localhost:8000/api/v1/users/verify%60"><code>http://localhost:8000/api/v1/users/verify</code></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752703870392/a534d04f-7cb9-4f84-92e1-e9844cfa6921.png" alt="Verify-Account" class="image--center mx-auto" width="1280" height="723" loading="lazy"></p>
<p>The image above shows that the verify token function is working well, and a status code of <code>200</code> is displayed.</p>
<h3 id="heading-login-function"><strong>Login Function</strong></h3>
<p>If you’ve reached this point, you’ve successfully signed up and verified a user’s account. </p>
<p>Now it’s time to create the login function, which allows a verified user to access their account. Here’s how you can do that:</p>
<p>Go to your <code>authController.js</code> file and create your login function by adding the following:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">exports</span>.login = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  <span class="hljs-keyword">const</span> { email, password } = req.body;

  <span class="hljs-comment">// 1. Validate email &amp; password presence</span>
  <span class="hljs-keyword">if</span> (!email || !password) {
    <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Please provide email and password"</span>, <span class="hljs-number">400</span>));
  }

  <span class="hljs-comment">// 2. Check if user exists and include password</span>
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email }).select(<span class="hljs-string">"+password"</span>);
  <span class="hljs-keyword">if</span> (!user || !(<span class="hljs-keyword">await</span> user.correctPassword(password, user.password))) {
    <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Incorrect email or password"</span>, <span class="hljs-number">401</span>));
  }

  <span class="hljs-comment">// 3. Create JWT token</span>
  <span class="hljs-keyword">const</span> token = signToken(user._id);

  <span class="hljs-comment">// 4. Configure cookie options</span>
  <span class="hljs-keyword">const</span> cookieOptions = {
    <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
      <span class="hljs-built_in">Date</span>.now() +
        (<span class="hljs-built_in">parseInt</span>(process.env.JWT_COOKIE_EXPIRES_IN, <span class="hljs-number">10</span>) || <span class="hljs-number">90</span>) <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> <span class="hljs-number">1000</span>
    ),
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-comment">// secure: process.env.NODE_ENV === "production",</span>
    <span class="hljs-comment">// sameSite: process.env.NODE_ENV === "production" ?</span>
    <span class="hljs-comment">//  "None" : "Lax",</span>

    <span class="hljs-comment">//set to false during or for local HTTP and cross-origin</span>
    <span class="hljs-attr">secure</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">sameSite</span>: <span class="hljs-string">"Lax"</span>,
  };

  <span class="hljs-comment">// 5. Send cookie</span>
  res.cookie(<span class="hljs-string">"token"</span>, token, cookieOptions);

});
</code></pre>
<p><code>if (!email || !password) {…}</code> checks if the user actually provided both an email and a password. If not, it returns the error: <code>Please provide email and password", 400</code>.</p>
<p><code>const user = await User.findOne({ email }).select("+password");</code> searches the database for a user with the provided email and explicitly includes the password field, since it’s normally hidden by default in the schema.</p>
<p><code>if (!user || !(await user.correctPassword(…))) {…}</code> checks if the user exists and if the password entered matches the one stored in the database (after hashing comparison). If either is wrong, it throws: <code>Incorrect email or password</code>.</p>
<p>The line <code>signToken(user._id)</code> generates a JWT using the user's unique ID. The <code>cookieOptions</code> object configures how the cookie behaves – it sets the cookie to expire after a specific number of days defined in the <code>.env</code> file, marks it as <code>httpOnly</code> to prevent JavaScript access for security, sets <code>secure</code> to <code>false</code> since the app is currently in development, and uses <code>sameSite: "Lax"</code> to allow cross-origin requests during local testing. </p>
<p>Finally, <code>res.cookie(...)</code> sends the token as a cookie attached to the HTTP response, enabling the client to store the token for authentication purposes.</p>
<p>From the code above, you may have noticed that the password stored in the database is hashed for security reasons. This means it looks completely different from the user's password when logging in. So, even if a user types in the correct password, it won't match the stored hash directly through a simple comparison. </p>
<p>To fix this, you need to compare the entered password with the hashed one using the <code>bcryptjs</code> package.</p>
<p>Head over to your <code>userModel.js</code> file and create a method that handles password comparison. This method will take the plain text password provided by the user and compare it to the hashed password stored in the database.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//userModel.js</span>
<span class="hljs-comment">//create a method responsible for comparing the password stored in the database</span>

userSchema.methods.correctPassword = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">password, userPassword</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.compare(password, userPassword);
};
</code></pre>
<p>This <code>correctPassword</code> method uses <code>bcrypt.compare()</code>, which internally hashes the plain password and checks if it matches the stored hashed version. This ensures that login validation works correctly and securely, even though the actual password is not stored in plain text.</p>
<p>Next, add the Login functionality to the <code>userRouter.js</code> file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> {login} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
<span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);

router.post(<span class="hljs-string">"/login"</span>, login);
</code></pre>
<p>You can test this endpoint using Postman with a <code>POST</code> request to: <a target="_blank" href="http://localhost:8000/api/v1/users/login%60"><code>http://localhost:8000/api/v1/users/login</code></a></p>
<h3 id="heading-logout-function"><strong>Logout Function</strong></h3>
<p>At this point, you can implement the logout function to end a user's session securely. To do this, navigate to your <code>authController.js</code> file and add the following function:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//creating a log out function</span>
<span class="hljs-built_in">exports</span>.logout = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  res.cookie(<span class="hljs-string">"token"</span>, <span class="hljs-string">"loggedout"</span>, {
    <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>),
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>,
  });

  res.status(<span class="hljs-number">200</span>).json({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Logged out successfully"</span>,
  });
});
</code></pre>
<p>This function works by overwriting the authentication cookie named <code>token</code> with the value <code>"loggedout"</code> and setting its expiration time to the past using <code>new Date(0)</code>. This effectively invalidates the cookie and removes it from the browser. </p>
<p>The <code>httpOnly: true</code> flag ensures that the cookie cannot be accessed via JavaScript, which protects it from XSS attacks, while the <code>secure</code> flag ensures that the cookie is only sent over HTTPS in a production environment. Once the cookie is cleared, a success response is returned with the message "Logged out successfully" to confirm the action.</p>
<p>Next, add the <code>logout</code> functionality to your route:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> {logout} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
<span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);

router.post(<span class="hljs-string">"/logout"</span>, logout);
</code></pre>
<p>Then, head to Postman to test your logout function and see if it works.</p>
<h2 id="heading-frontend-setup"><strong>Frontend Setup</strong></h2>
<p>Now that your backend is up and running, you can integrate it into your frontend application.</p>
<p>First, navigate to the frontend directory using the command <code>cd Frontend</code>.</p>
<p>Create a new folder in the <code>src</code> folder where your authentication-related files will live. Depending on your preference or app structure, you can name it something like <code>auth</code> or <code>pages</code>. Then, create a new file called <code>NewUser. js</code>. This file will handle user signup functionality.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> { Loader } <span class="hljs-keyword">from</span> <span class="hljs-string">'lucide-react'</span>;
<span class="hljs-keyword">import</span> { useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>;
<span class="hljs-keyword">import</span> { setAuthUser, setPendingEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../store/authSlice'</span>;

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NewUser</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> dispatch = useDispatch();
  <span class="hljs-keyword">const</span> navigate = useNavigate();

  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">const</span> [formData, setFormData] = useState({
    <span class="hljs-attr">username</span>: <span class="hljs-string">''</span>,
    <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
    <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
    <span class="hljs-attr">passwordConfirm</span>: <span class="hljs-string">''</span>,
  });

  <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { name, value } = e.target;
    setFormData(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({ ...prev, [name]: value }));
  };

  <span class="hljs-keyword">const</span> submitHandler = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    setLoading(<span class="hljs-literal">true</span>);
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/signup`</span>, formData, {
        <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
      });
      <span class="hljs-keyword">const</span> user = response.data.data.user;
      dispatch(setAuthUser(user));
      dispatch(setPendingEmail(formData.email)); <span class="hljs-comment">// Save email for OTP</span>
      navigate(<span class="hljs-string">'/verifyAcct'</span>); <span class="hljs-comment">// Navigate to OTP verification page</span>
    } <span class="hljs-keyword">catch</span> (error) {
      alert(error.response?.data?.message || <span class="hljs-string">'Signup failed'</span>);
    } <span class="hljs-keyword">finally</span> {
      setLoading(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
// visit the frontend Github repository to see the remaining code for the OTP Verification

https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/Join/NewUser.jsx
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NewUser;
</code></pre>
<p>The code above renders a signup form with fields for <code>username</code>, <code>email</code>, <code>password</code> and <code>passwordConfirm</code>. When the user submits the form, the frontend sends a <code>POST</code> request to the backend’s <code>/users/signup</code> endpoint using <code>Axios</code>. The <code>withCredentials: true</code> option ensures cookies like the <code>auth token</code> are properly set by the backend.</p>
<p>If the signup is successful, the user data is dispatched into Redux using <code>setAuthUser()</code>, and their email is saved with <code>setPendingEmail()</code> so it can be used during <code>OTP</code> verification. Then, the user is redirected to the <code>/verifyAcct</code> route, where they can enter their <code>OTP</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752704266192/0d1d5891-000a-48dc-a1d8-306a0103824a.png" alt="Frontend-Sign-Up" class="image--center mx-auto" width="815" height="675" loading="lazy"></p>
<h3 id="heading-otp-verification-page"><strong>OTP Verification Page</strong></h3>
<p>The OTP verification page is the next step in the user authentication process. Once a user signs up, they are redirected to enter the 4-digit OTP sent to their email. This verifies their account before allowing login access.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useSelector, useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>;
<span class="hljs-keyword">import</span> { useNavigate, Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> { clearPendingEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../store/authSlice'</span>;

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'http://localhost:5000/api'</span>; <span class="hljs-comment">// adjust as needed</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VerifyAcct</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [code, setCode] = useState([<span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-string">''</span>]);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [resendLoading, setResendLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [timer, setTimer] = useState(<span class="hljs-number">60</span>);

  <span class="hljs-keyword">const</span> inputsRef = useRef([]);
  <span class="hljs-keyword">const</span> dispatch = useDispatch();
  <span class="hljs-keyword">const</span> navigate = useNavigate();
  <span class="hljs-keyword">const</span> email = useSelector(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> state.auth.pendingEmail);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> interval;
    <span class="hljs-keyword">if</span> (timer &gt; <span class="hljs-number">0</span>) {
      interval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> setTimer(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev - <span class="hljs-number">1</span>), <span class="hljs-number">1000</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">clearInterval</span>(interval);
  }, [timer]);

  <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">value, index</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/^\d*$/</span>.test(value)) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> newCode = [...code];
<span class="hljs-comment">// visit the frontend Github repository to see the remaining code for the OTP Verification</span>
https:<span class="hljs-comment">//github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/VerifyAcct.jsx</span>
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> VerifyAcct;
</code></pre>
<p>Here’s what the code does: </p>
<p>The OTP is stored as an array of 4 characters (<code>[‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘]</code>). Each box only accepts digits, and focus automatically moves to the next input as the user types in the digit. The focus returns to the previous input box if the user presses the backspace button on an empty box.</p>
<p>When the OTP has been added and the form is submitted, the 4-digit code is joined into a string and an <code>HTTP POST</code> request is made to the backend <code>/user/verify/</code> endpoint along with the stored email and OTP. If the verification is successful, the user is alerted and redirected to the login page, and if not, an error is shown.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752704448954/8ea46e32-c6d9-42e1-a016-f04b259eb0e7.png" alt="Frontend-OTP" class="image--center mx-auto" width="798" height="631" loading="lazy"></p>
<h3 id="heading-log-in"><strong>Log In</strong></h3>
<p>Now you can create the login interface for your application. First, create a <code>Login.jsx</code> file and input the code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Login.Jsx</span>

<span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Join</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [formData, setFormData] = useState({ <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>, <span class="hljs-attr">password</span>: <span class="hljs-string">''</span> });
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> navigate = useNavigate();

  <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  <span class="hljs-keyword">const</span> handleLogin = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    setLoading(<span class="hljs-literal">true</span>);
    setError(<span class="hljs-string">''</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/login`</span>, formData, {
        <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
      });

      <span class="hljs-keyword">if</span> (res.data.status === <span class="hljs-string">'success'</span>) {
        <span class="hljs-keyword">const</span> { token, user, streamToken } = res.data;

        <span class="hljs-comment">// Save to localStorage</span>
        <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'authToken'</span>, token);
        <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'user'</span>, <span class="hljs-built_in">JSON</span>.stringify(user));
        <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'streamToken'</span>, streamToken);

        navigate(<span class="hljs-string">'/dashboard'</span>);
      }
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.error(err);
      setError(
        err.response?.data?.message || <span class="hljs-string">'Something went wrong. Please try again.'</span>
      );
    } <span class="hljs-keyword">finally</span> {
      setLoading(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
{// visit the frontend Github repository to see the remaining code for the OTP Verification
https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/Login.jsx
 <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
}
</code></pre>
<p>The <code>Export default Join;</code> component allows a registered and verified user to log into your application using their email and password. It handles form submission, talks to the backend, and securely stores user data if login is successful. </p>
<p><code>handleChange()</code> updates the email or password field as the user types. </p>
<p><code>handleLogin()</code> is triggered when the login form is submitted. When the login button is triggered, it sends a <code>Post</code> request to <code>/users/login</code> with the form data, which includes <code>{withCredentials: true}</code> to enable cookie handling. </p>
<p>If login is successful, it extracts the JWT token, user data, and Stream Chat token from the response and stores them in the <code>localStorage</code> so the user stays logged in across sessions. Then it redirects the user to the dashboard page using <code>navigate(‘/dashboard’)</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752704515845/55c6e74a-a8bc-462a-b988-67b0e8df40ac.png" alt="Frontend-Login" class="image--center mx-auto" width="859" height="583" loading="lazy"></p>
<h3 id="heading-set-up-the-frontend-route"><strong>Set Up the Frontend Route</strong></h3>
<p>Just as you set up the backend route, you have to do the same for the frontend.</p>
<p>Head to <code>App.jsx</code>. Before adding the route, make sure you have installed the <code>react-router-dom</code> package. If not, run this command in the frontend terminal: </p>
<p><code>npm install react-router-dom</code></p>
<p>Then, add the command to your <code>App.jsx</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { createBrowserRouter, RouterProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> HomeIndex <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Home/HomeIndex'</span>;
<span class="hljs-keyword">import</span> Hero <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Home/Hero'</span>;

<span class="hljs-comment">//Authentication Section</span>
<span class="hljs-keyword">import</span> NewUser <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/Join/NewUser'</span>;
<span class="hljs-keyword">import</span> Login <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/login/Login'</span>
<span class="hljs-keyword">import</span> VerifyAcct <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/login/VerifyAcct'</span>;

<span class="hljs-comment">// Dashboard</span>
<span class="hljs-keyword">import</span> Dashboard <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Dashboard/Dashboard'</span>;
<span class="hljs-keyword">import</span> VideoStream <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/VideoStream'</span>;

<span class="hljs-keyword">const</span> router = createBrowserRouter([
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
    <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">HomeIndex</span> /&gt;</span></span>,
    children: [
      { <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Hero</span> /&gt;</span></span> }
    ],
  },

  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">'signup'</span>,
    <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">NewUser</span> /&gt;</span></span>,
    children: [
      { <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">NewUser</span> /&gt;</span></span> }
    ],
  },

  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">'login'</span>,
    <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Login</span> /&gt;</span></span>,
    children: [
      {<span class="hljs-attr">index</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Login</span> /&gt;</span></span>}
    ]
  },

]);
{<span class="hljs-comment">// visit the frontend Github repository to see the remaining code for the OTP Verification</span>
<span class="hljs-attr">https</span>:<span class="hljs-comment">//github.com/Derekvibe/Telehealth_Frontend/blob/main/src/App.jsx}</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'border border-red-700 w-full min-w-[100vw] min-h-[100vh]'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">RouterProvider</span> <span class="hljs-attr">router</span>=<span class="hljs-string">{router}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-stream-chat-and-video-integration"><strong>Stream Chat and Video Integration</strong></h2>
<p>Before proceeding to the dashboard, let’s integrate the Stream <a target="_blank" href="https://getstream.io/chat/">Chat</a> and <a target="_blank" href="https://getstream.io/video/">Video</a> functionality into the project.</p>
<p>First, <a target="_blank" href="https://getstream.io/try-for-free/">create a free Stream account</a>, start a new project in your dashboard, and get your <code>APP KEY</code> and <code>API_SECRET</code>.</p>
<pre><code class="lang-javascript">STREAM_API_KEY=your_app_key
STREAM_API_SECRET=your_api_secret
</code></pre>
<p>Watch the Stream <a target="_blank" href="https://youtu.be/kGKq4giL4ok?si=M_nkWAiq4IzGNYD_">Chat React Quick Start Guide</a> to see how you can set it up.</p>
<p>Next, store your Stream <code>APP KEY</code> and <code>API_SECRET</code> in your <code>.env</code>.</p>
<h3 id="heading-install-stream-packages-frontend"><strong>Install Stream Packages (Frontend)</strong></h3>
<p>Now, install the Stream Chat and Video packages in your terminal.</p>
<pre><code class="lang-javascript">npm install stream-chat stream-chat-react
npm install @stream-io/video-react-sdk
npm install @stream-io/stream-chat-css
</code></pre>
<h3 id="heading-stream-token-handler"><strong>Stream Token Handler</strong></h3>
<p>First, create a new file in your frontend Src directory and name it. In this example, it’s <code>StreamContext.jsx</code>. This file sets up a context to fetch and manage the Stream Chat token on login and includes logout functionality.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useContext, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;

<span class="hljs-comment">// 1. Create the context</span>
<span class="hljs-keyword">const</span> StreamContext = createContext();

<span class="hljs-comment">// 2. Provider component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> StreamProvider = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchToken = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/stream/get-token`</span>, {
          <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
        });

        <span class="hljs-keyword">if</span> (res.data?.user &amp;&amp; res.data?.token) {
          setUser(res.data.user);
          setToken(res.data.token);
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream user/token:"</span>, res.data);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Token or user missing in response:"</span>, res.data);
        }
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching Stream token:"</span>, error);
      }
    };

    fetchToken();
  }, []);

  <span class="hljs-comment">//Log out Functionality</span>
  <span class="hljs-keyword">const</span> logout = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/logout`</span>, {},
        {
          <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>
        });

      <span class="hljs-comment">// Clear localStorage</span>
      <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'authToken'</span>);
      <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'user'</span>);
      <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'streamToken'</span>);

      <span class="hljs-comment">// Clear context</span>
      setUser(<span class="hljs-literal">null</span>);
      setToken(<span class="hljs-literal">null</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Logout failed"</span>, error);
    }
  };

  <span class="hljs-comment">// Expose Logout with capital L</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">StreamContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">user</span>, <span class="hljs-attr">token</span>, <span class="hljs-attr">Logout:logout</span> }}&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">StreamContext.Provider</span>&gt;</span></span>
  );
};

<span class="hljs-comment">// 3. Custom hook for easy access</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useStream = <span class="hljs-function">() =&gt;</span> useContext(StreamContext);
</code></pre>
<p>The code above creates a StreamContext using React’s Context API. In the <code>useEffect</code> section, it makes a <code>GET</code> request to <code>/stream/get-token</code> to fetch the authenticated user and their Stream token. Then it stores them in <code>user</code> and <code>token</code> states. It also provides the user/token through the context so that any component that needs it can make use of it.</p>
<p>Finally, it adds a <code>Logout</code> method that hits the logout endpoint and clears all stored auth data from <code>localStorage</code>.</p>
<p>Next, open your <code>main.jsx</code> and wrap your entire application with the <code>StreamProvider</code> so all child components can access the Stream context.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// main.jsx</span>
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;
<span class="hljs-keyword">import</span> { StreamProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/StreamContext'</span>;

createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)).render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">StreamProvider</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">StreamProvider</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">StrictMode</span>&gt;</span></span>
);
</code></pre>
<h3 id="heading-set-up-stream-api"><strong>Set Up Stream API</strong></h3>
<p>After successfully creating the streamContent, the next step is to set up the Stream API. This will be the endpoint from which the user ID and user Stream token can be generated and fetched during login.</p>
<p>To set it up, navigate to your backend directory by running <code>cd Backend</code> in your terminal. Then install the Stream package using the command:</p>
<pre><code class="lang-javascript">npm install getstream
npm install stream-chat stream-chat-react
</code></pre>
<p>Open your <code>.env</code> file and add your Stream <code>API KEY</code> and <code>API_SECRET</code>:</p>
<pre><code class="lang-javascript">STREAM_API_KEY=your_app_key
STREAM_API_SECRET=your_api_secret
</code></pre>
<p>Next, open your <code>authController.js</code> and create your Stream Chat logic:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Initialize the Stream Client</span>
<span class="hljs-keyword">const</span> {StreamChat} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stream-chat"</span>);
<span class="hljs-keyword">const</span> streamClient = StreamChat.getInstance(
  process.env.STREAM_API_KEY,
  process.env.STREAM_API_SECRET
);

<span class="hljs-comment">// Modifies the `createSendToken to include `streamToken`</span>
<span class="hljs-keyword">const</span> createSendToken = <span class="hljs-function">(<span class="hljs-params">user, statusCode, res, message</span>) =&gt;</span> {
……
<span class="hljs-keyword">const</span> streamToken = streamClient.createToken(user._id.toString());

  <span class="hljs-comment">//structure of the cookie response when sent to the user</span>
  res.status(statusCode).json({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
    message,
    token,
    streamToken,
    <span class="hljs-attr">data</span>: {
      <span class="hljs-attr">user</span>: {
        <span class="hljs-attr">id</span>: user._id.toString(),
        <span class="hljs-attr">name</span>: user.username,
      },
    },
  });
};

<span class="hljs-comment">//login functionality</span>
<span class="hljs-built_in">exports</span>.login = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
 {…………………..}

<span class="hljs-comment">// Generate Stream token</span>
  <span class="hljs-keyword">await</span> streamClient.upsertUser({
    <span class="hljs-attr">id</span>: user._id.toString(),
    <span class="hljs-attr">name</span>: user.username,
  });
  <span class="hljs-keyword">const</span> streamToken = streamClient.createToken(user._id.toString());

user.password = <span class="hljs-literal">undefined</span>;

  res.status(<span class="hljs-number">200</span>).json({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Login successful"</span>,
    token,
    <span class="hljs-attr">user</span>: {
      <span class="hljs-attr">id</span>: user._id.toString(),
      <span class="hljs-attr">name</span>: user.username,
    },
    streamToken,
  });
</code></pre>
<h3 id="heading-streamroutes-endpoint"><code>streamRoutes</code> <strong>Endpoint</strong></h3>
<p>Next, create an endpoint from which the Stream token can be called. To do this, go to your routes folder and create a new file called <code>streamRoutes.js</code>. In <code>streamRoutes.js</code>, add the command:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> { StreamChat } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stream-chat"</span>);

<span class="hljs-keyword">const</span> protect = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/protect"</span>);

<span class="hljs-keyword">const</span> router = express.Router();

<span class="hljs-keyword">const</span> apiKey = process.env.STREAM_API_KEY;
<span class="hljs-keyword">const</span> apiSecret = process.env.STREAM_API_SECRET;

<span class="hljs-keyword">if</span> (!apiKey || !apiSecret) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
    <span class="hljs-string">"Missing Stream credentials. Check your environment variables."</span>
  );
}

<span class="hljs-keyword">const</span> streamClient = StreamChat.getInstance(apiKey, apiSecret);

router.get(<span class="hljs-string">"/get-token"</span>, protect, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { id, username } = req.user || {};
    <span class="hljs-built_in">console</span>.log(req.user.id, <span class="hljs-string">"User"</span>);
    <span class="hljs-comment">// TRY LOGGING THE ID AND NAME FROM YOUR REQUEST FIRST</span>

    <span class="hljs-keyword">if</span> (!id || !username) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid user data"</span> });
    }

    <span class="hljs-comment">// const userId = _id.toString();</span>
    <span class="hljs-keyword">const</span> user = { id, username };

    <span class="hljs-comment">// Ensure user exists in Stream backend</span>
    <span class="hljs-keyword">await</span> streamClient.upsertUser(user);

    <span class="hljs-comment">// Add user to my_general_chat channel</span>
    <span class="hljs-keyword">const</span> channel = streamClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>);
    <span class="hljs-keyword">await</span> channel.addMembers([id]);


    <span class="hljs-comment">// Generate token</span>
    <span class="hljs-keyword">const</span> token = streamClient.createToken(id);
    res.status(<span class="hljs-number">200</span>).json({ token, user });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Stream token generation error:"</span>, error);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to generate Stream token"</span> });
  }
});

<span class="hljs-comment">/**
 * @route   POST /api/stream/token
 * @desc    Generate a Stream token for any userId from request body (no auth)
 * @access  Public
 */</span>
router.post(<span class="hljs-string">"/token"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { userId, name } = req.body;

    <span class="hljs-keyword">if</span> (!userId) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"userId is required"</span> });
    }

    <span class="hljs-keyword">const</span> userName = name || <span class="hljs-string">"Anonymous"</span>;
    <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">id</span>: userId, <span class="hljs-attr">name</span>: userName };

    <span class="hljs-keyword">await</span> streamClient.upsertUser(user);

    <span class="hljs-comment">// Add user to my_general_chat channel</span>
    <span class="hljs-keyword">const</span> channel = streamClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>);
    <span class="hljs-keyword">await</span> channel.addMembers([userId]);


    <span class="hljs-keyword">const</span> token = streamClient.createToken(userId);

    res.status(<span class="hljs-number">200</span>).json({
      token,
      <span class="hljs-attr">user</span>: {
        <span class="hljs-attr">id</span>: userId,
        <span class="hljs-attr">name</span>: name,
        <span class="hljs-attr">role</span>: <span class="hljs-string">"admin"</span>,
        <span class="hljs-attr">image</span>: <span class="hljs-string">`https://getstream.io/random_png/?name=<span class="hljs-subst">${name}</span>`</span>,
      },
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Public token generation error:"</span>, error);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to generate token"</span> });
  }
});

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h2 id="heading-user-logout-endpoint"><strong>User Logout Endpoint</strong></h2>
<p>Go to your <code>authController.js</code> and create a functionality that handles logging out the user:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">exports</span>.logout = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
  res.cookie(<span class="hljs-string">"token"</span>, <span class="hljs-string">"loggedout"</span>, {
    <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>),
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>,
  });

  res.status(<span class="hljs-number">200</span>).json({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Logged out successfully"</span>,
  });
});
</code></pre>
<p>Then register your logout route to your <code>userRouters.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> {logout}= <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
<span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);


router.post(<span class="hljs-string">"/logout"</span>, isAuthenticated, logout);

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h2 id="heading-chat-and-video-function-frontend">Chat and Video Function (Frontend)</h2>
<p>After setting up your backend Stream API, the last task is setting up chat and video in your frontend application.</p>
<h3 id="heading-dashboardjsx"><code>Dashboard.jsx</code></h3>
<p>Create a new file <code>Dashboard.jsx</code> in your frontend directory. This is where you will set up your Stream and video function.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
<span class="hljs-keyword">import</span> {
  Chat,
  Channel,
  ChannelHeader,
  MessageInput,
  MessageList,
  Thread,
  Window,
  useCreateChatClient,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"stream-chat-react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"stream-chat-react/dist/css/v2/index.css"</span>;
<span class="hljs-keyword">import</span> { useStream } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/StreamContext"</span>;
<span class="hljs-keyword">import</span> VideoStream <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/VideoStream"</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;



<span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.VITE_STREAM_API_KEY;

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [channel, setChannel] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [clientReady, setClientReady] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> navigate = useNavigate();

  <span class="hljs-comment">// const ChatComponent = () =&gt; {</span>
    <span class="hljs-keyword">const</span> { user, token, Logout } = useStream();

    <span class="hljs-comment">// Always call the hook</span>
    <span class="hljs-keyword">const</span> chatClient = useCreateChatClient({
      apiKey,
      <span class="hljs-attr">tokenOrProvider</span>: token,
      <span class="hljs-attr">userData</span>: user?.id ? { <span class="hljs-attr">id</span>: user.id } : <span class="hljs-literal">undefined</span>,
    });

  <span class="hljs-comment">// Debug: See when user/token is ready</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream user:"</span>, user);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream token:"</span>, token);
  }, [user, token]);

    <span class="hljs-comment">// Connect user to Stream</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> connectUser = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">if</span> (!chatClient || !user || !token || !user?.id) {
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Missing chat setup data:"</span>, { chatClient, token, user });
          <span class="hljs-keyword">return</span>;
        }


        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> chatClient.connectUser(
            {
              <span class="hljs-attr">id</span>: user.id,
              <span class="hljs-attr">name</span>: user.name || <span class="hljs-string">"Anonymous"</span>,
              <span class="hljs-attr">image</span>:
                user.image ||
                <span class="hljs-string">`https://getstream.io/random_png/?name=<span class="hljs-subst">${user.name || <span class="hljs-string">"user"</span>}</span>`</span>,
            },
            token
          );

          <span class="hljs-keyword">const</span> newChannel = chatClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>, {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"General Chat"</span>,
            <span class="hljs-attr">members</span>: [user.id],
          });

          <span class="hljs-keyword">await</span> newChannel.watch();
          setChannel(newChannel);
          setClientReady(<span class="hljs-literal">true</span>);
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error connecting user:"</span>, err);
        }
      };

      connectUser();
    }, [chatClient, user, token]);

    <span class="hljs-keyword">const</span> handleVideoCallClick = <span class="hljs-function">() =&gt;</span> {
      navigate(<span class="hljs-string">"/videoCall"</span>);
  };

  <span class="hljs-keyword">const</span> handleLogout = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> Logout();
    navigate(<span class="hljs-string">"/login"</span>);
  }

  <span class="hljs-keyword">if</span> (!user || !token) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-600"</span>&gt;</span>User or token not ready.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
  }

  <span class="hljs-keyword">if</span> (!clientReady || !channel) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Loading chat...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;


  <span class="hljs-keyword">return</span> (
{ checkout the github repo}
            &lt;ChannelHeader /&gt;
            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">MessageList</span> /&gt;</span></span>
            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">MessageInput</span> /&gt;</span></span>
          &lt;/Window&gt;
          <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Thread</span> /&gt;</span></span>
        &lt;/Channel&gt;
      &lt;/Chat&gt;


      &lt;/div&gt;

    );
  }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h3 id="heading-video-setup"><strong>Video Setup</strong></h3>
<p>You’ll now set up the video function for your frontend. To do so, create a new file <code>VideoStream.jsx</code> and add the command:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { StreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-client"</span>;
<span class="hljs-keyword">import</span> { StreamVideo, StreamCall } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;


<span class="hljs-keyword">import</span> { useStream } <span class="hljs-keyword">from</span> <span class="hljs-string">"./StreamContext"</span>;
<span class="hljs-keyword">import</span> { MyUILayout } <span class="hljs-keyword">from</span> <span class="hljs-string">"./MyUILayout"</span>;


<span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.VITE_STREAM_API_KEY;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VideoStream</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> [client, setClient] = useState(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [call, setCall] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> { user, token } = useStream();
  <span class="hljs-keyword">const</span> navigate = useNavigate();

  useEffect(<span class="hljs-function">() =&gt;</span> {

    <span class="hljs-keyword">let</span> clientInstance;
    <span class="hljs-keyword">let</span> callInstance;


    <span class="hljs-keyword">const</span> setup = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">if</span> (!apiKey || !user || !token) <span class="hljs-keyword">return</span>;

      clientInstance = <span class="hljs-keyword">new</span> StreamVideoClient({ apiKey, user, token });

      callInstance = clientInstance.call(<span class="hljs-string">"default"</span>, user.id); <span class="hljs-comment">// Use user.id as callId</span>


      <span class="hljs-keyword">await</span> callInstance.join({ <span class="hljs-attr">create</span>: <span class="hljs-literal">true</span> });

      setClient(clientInstance);
      setCall(callInstance);
    };

    setup();

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (callInstance) callInstance.leave();
      <span class="hljs-keyword">if</span> (clientInstance) clientInstance.disconnectUser();

    };
  }, [user, token]);

  <span class="hljs-keyword">const</span> handleLeaveCall = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">if</span> (call) <span class="hljs-keyword">await</span> call.leave();
    <span class="hljs-keyword">if</span> (client) <span class="hljs-keyword">await</span> client.disconnectUser();

    setCall(<span class="hljs-literal">null</span>);
    setClient(<span class="hljs-literal">null</span>);

    navigate(<span class="hljs-string">"/dashboard"</span>); <span class="hljs-comment">// or any other route</span>
  };

  <span class="hljs-keyword">if</span> (!apiKey) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Missing Stream API Key<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;

  <span class="hljs-keyword">if</span> (!client || !call)
    <span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center h-screen text-xl font-semibold"</span>&gt;</span>
    Connecting to the video call...
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-screen w-full p-2 sm:p-4 bg-gray-50"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">StreamVideo</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{client}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StreamCall</span> <span class="hljs-attr">call</span>=<span class="hljs-string">{call}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">MyUILayout</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">StreamCall</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">StreamVideo</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLeaveCall}</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">"absolute top-2 right-2 sm:top-4 sm:right-4 bg-red-600 text-white text-sm sm:text-base px-3 sm:px-4 py-1.5 sm:py-2 rounded-lg shadow hover:bg-red-700 transition"</span>
      &gt;</span>
        Leave Call
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
  }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> VideoStream;
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">//MYUILayout.jsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {
  useCall,
  useCallStateHooks,
  CallingState,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@stream-io/video-react-sdk'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyUILayout</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> call = useCall();
  <span class="hljs-keyword">const</span> { useCallCallingState, useParticipantCount } = useCallStateHooks();
  <span class="hljs-keyword">const</span> callingState = useCallCallingState();
  <span class="hljs-keyword">const</span> participantCount = useParticipantCount();

  <span class="hljs-keyword">if</span> (callingState !== CallingState.JOINED) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Joining call...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>', <span class="hljs-attr">fontSize:</span> '<span class="hljs-attr">1.2rem</span>' }}&gt;</span>
      ✅ Call "<span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{call?.id}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>" has <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{participantCount}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> participants.
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-project-demo">Project Demo</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752705861841/85b6d6b3-0f5e-402f-b8b5-8ab51d820403.gif" alt="Telehealth Final Project Demo" class="image--center mx-auto" width="1398" height="630" loading="lazy"></p>
<p>Congratulations! You have successfully integrated Stream’s chat and video function into your application.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>And that’s a wrap! </p>
<p>You’ve <a target="_blank" href="https://getstream.io/blog/telemedicine-app-development/">built a telehealth app</a> with secure video, real-time chat, and user authentication – all powered by Stream’s Chat and Video SDKs. </p>
<p>This foundation gives you the flexibility to expand further with features like appointment scheduling, patient history, or HIPPA-compliant file sharing. </p>
<p>You can find the <a target="_blank" href="https://github.com/Derekvibe/Telehealth_Backend">frontend</a> and <a target="_blank" href="https://github.com/Derekvibe/Telehealth_Frontend">backend</a> applications on GitHub. The frontend app is hosted using the Vercel hosting service, and the backend is hosted on Render.</p>
<p>Check out the <a target="_blank" href="https://telehealth-frontend.vercel.app/">repository of the application</a>.</p>
<p>Happy coding! 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Make a Dropdown Menu with shadcn/ui ]]>
                </title>
                <description>
                    <![CDATA[ Dropdown menus are little pop-up menus that help you show more options without cluttering your screen. They’re super helpful in websites and apps. In this guide, you’ll learn how to build a dropdown menu using shadcn/ui. It’s a tool that works well w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/shadcn-ui-dropdown-menu/</link>
                <guid isPermaLink="false">687964f6562b6ce291fda4e2</guid>
                
                    <category>
                        <![CDATA[ shadcn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Kalal ]]>
                </dc:creator>
                <pubDate>Thu, 17 Jul 2025 21:02:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752786132476/fef60fd2-ad5e-4f9d-9dcf-de4b99adac99.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Dropdown menus are little pop-up menus that help you show more options without cluttering your screen. They’re super helpful in websites and apps.</p>
<p>In this guide, you’ll learn how to build a dropdown menu using shadcn/ui. It’s a tool that works well with Tailwind CSS and Radix UI to help you make nice-looking, easy-to-use menus.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-shadcnui">What is shadcn/ui?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-shadcnui-for-dropdowns">Why Use shadcn/ui for Dropdowns?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-build-a-dropdown-step-by-step">Let’s Build a Dropdown Step-by-Step</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-start-a-new-project">Step 1: Start a New Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-add-the-dropdown-menu-component">Step 2: Add the Dropdown Menu Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-import-what-you-need">Step 3: Import What You Need</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-build-a-simple-dropdown">Step 4: Build a Simple Dropdown</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-make-it-look-better">Step 5: Make It Look Better</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-make-it-work-on-all-screens">Step 6: Make It Work on All Screens</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-7-add-cool-icons">Step 7: Add Cool Icons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-8-its-already-accessible">Step 8: It’s Already Accessible!</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-use-case-country-dropdown-with-flags">Real-World Use Case: Country Dropdown with Flags</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">💡 Prerequisites</h3>
<p>Before we start, make sure you have:</p>
<ul>
<li><p>Basic knowledge of React and JavaScript</p>
</li>
<li><p>Node.js and a package manager like npm, pnpm, or yarn are installed</p>
</li>
<li><p>Familiarity with Tailwind CSS is a bonus, but not required</p>
</li>
</ul>
<p>We’ll walk through everything step by step, so don’t worry if you’re not an expert yet.</p>
<h2 id="heading-what-is-shadcnui">What is shadcn/ui?</h2>
<p><a target="_blank" href="https://ui.shadcn.com/docs/installation">shadcn/ui</a> is a group of tools (called components) that help you build parts of a website, like buttons, modals, and dropdowns. It’s built with Radix UI and styled using Tailwind CSS. It’s perfect if you’re using React or Next.js.</p>
<p>With shadcn/ui, you don’t get just styled components, you get full control over how everything works and looks. That makes it perfect for teams that want consistency in design without giving up flexibility.</p>
<h3 id="heading-why-use-shadcnui-for-dropdowns">Why Use shadcn/ui for Dropdowns?</h3>
<p>Dropdown menus are a great use case for shadcn/ui because:</p>
<ul>
<li><p>It’s easy to use with keyboard and screen readers</p>
</li>
<li><p>You can create custom looks using Tailwind CSS</p>
</li>
<li><p>You control how it works and looks</p>
</li>
<li><p>It works great in real websites and apps</p>
</li>
<li><p>It integrates well with modern React workflows</p>
</li>
</ul>
<h2 id="heading-lets-build-a-dropdown-step-by-step">Let’s Build a Dropdown Step-by-Step</h2>
<h3 id="heading-step-1-start-a-new-project-with-shadcnui">Step 1: Start a New Project with shadcn/ui</h3>
<p>You don’t need to set up React, Next.js, or Tailwind manually. Just run this command:</p>
<pre><code class="lang-bash">pnpm dlx shadcn@latest init
</code></pre>
<p>This will automatically create a new Next.js app with Tailwind CSS and shadcn/ui preconfigured.</p>
<p>Tip: You can also use <code>npx</code> instead of <code>pnpm dlx</code> if you prefer:</p>
<pre><code class="lang-bash">npx shadcn@latest init
</code></pre>
<h3 id="heading-step-2-add-the-dropdown-menu-component">Step 2: Add the Dropdown Menu Component</h3>
<p>After your project is ready, add the dropdown component using:</p>
<pre><code class="lang-bash">npx shadcn@latest add dropdown-menu
</code></pre>
<p>This will pull in all the necessary components to create a dropdown menu.</p>
<h3 id="heading-step-3-import-what-you-need">Step 3: Import What You Need</h3>
<p>In your React file, import the full dropdown module so you can access all its features:</p>
<pre><code class="lang-tsx">import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuGroup,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuPortal,
} from "@/components/ui/dropdown-menu"
</code></pre>
<h3 id="heading-step-4-build-a-simple-dropdown">Step 4: Build a Simple Dropdown</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752690572839/4cb2bd61-b843-4fe3-8530-4b341d38a633.jpeg" alt="Screenshot of basic dropdown we're building" class="image--center mx-auto" width="630" height="577" loading="lazy"></p>
<p>Here’s a basic dropdown example:</p>
<pre><code class="lang-tsx">export function ProfileMenu() {
  return (
    &lt;DropdownMenu&gt;
      &lt;DropdownMenuTrigger asChild&gt;
        &lt;button className="px-4 py-2 bg-primary text-white rounded"&gt;
          Open Menu
        &lt;/button&gt;
      &lt;/DropdownMenuTrigger&gt;
      &lt;DropdownMenuContent className="w-56"&gt;
        &lt;DropdownMenuLabel&gt;My Account&lt;/DropdownMenuLabel&gt;
        &lt;DropdownMenuSeparator /&gt;
        &lt;DropdownMenuItem&gt;Profile&lt;/DropdownMenuItem&gt;
        &lt;DropdownMenuItem&gt;Settings&lt;/DropdownMenuItem&gt;
        &lt;DropdownMenuItem&gt;Log out&lt;/DropdownMenuItem&gt;
      &lt;/DropdownMenuContent&gt;
    &lt;/DropdownMenu&gt;
  )
}
</code></pre>
<p>This is just the start. You can add groups, submenus, and keyboard shortcuts for power users.</p>
<h3 id="heading-step-5-make-it-look-better">Step 5: Make It Look Better</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752690441156/0c2b8e39-72ca-4823-8dd2-6af305f02275.jpeg" alt="Screenshot showing dropdown with styling applied" class="image--center mx-auto" width="671" height="601" loading="lazy"></p>
<p>Use Tailwind CSS to style your dropdown, and hover effects like this:</p>
<pre><code class="lang-tsx">&lt;DropdownMenu&gt;
        &lt;DropdownMenuTrigger asChild&gt;
          &lt;button className="px-3 py-1.5 bg-primary text-white text-sm font-medium rounded-md hover:bg-primary/90 transition-colors"&gt;
            Open Menu
          &lt;/button&gt;
        &lt;/DropdownMenuTrigger&gt;
        &lt;DropdownMenuContent className="w-52 border-gray-200 shadow-lg rounded-md space-y-0.5"&gt;
          &lt;DropdownMenuLabel className="text-xs text-gray-500"&gt;
            My Account
          &lt;/DropdownMenuLabel&gt;
          &lt;DropdownMenuSeparator className="border-t border-gray-100" /&gt;
          &lt;DropdownMenuItem className="px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100 rounded-md cursor-pointer transition-colors"&gt;
            Profile
          &lt;/DropdownMenuItem&gt;
          &lt;DropdownMenuItem className="px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100 rounded-md cursor-pointer transition-colors"&gt;
            Settings
          &lt;/DropdownMenuItem&gt;
          &lt;DropdownMenuItem className="px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 rounded-md cur
</code></pre>
<h3 id="heading-step-6-make-it-work-on-all-screens">Step 6: Make It Work on All Screens</h3>
<p>Want your dropdown to be responsive? Use Tailwind’s responsive classes:</p>
<pre><code class="lang-tsx">&lt;DropdownMenuContent className="w-full md:w-64"&gt;
</code></pre>
<p>You can also dynamically position the dropdown using Radix's built-in portal support.</p>
<h3 id="heading-step-7-add-cool-icons">Step 7: Add Cool Icons</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752691587711/0a96c5ca-0fa2-4916-92d2-087f2071d40e.jpeg" alt="Screenshot of dropdown with icons added" class="image--center mx-auto" width="618" height="565" loading="lazy"></p>
<p>Install Lucide icons:</p>
<pre><code class="lang-bash">npm install lucide-react
</code></pre>
<p>Then use them in your menu:</p>
<pre><code class="lang-tsx">import { User, Settings, LogOut } from "lucide-react"

&lt;DropdownMenuItem&gt;
  &lt;User className="mr-2 h-4 w-4" /&gt; Profile
&lt;/DropdownMenuItem&gt;
</code></pre>
<p>Icons help users scan options quickly – a great touch for UX.</p>
<h3 id="heading-step-8-its-already-accessible">Step 8: It’s Already Accessible!</h3>
<p>shadcn/ui (thanks to Radix UI) makes your dropdown menu:</p>
<ul>
<li><p>Keyboard friendly</p>
</li>
<li><p>Screen-reader ready</p>
</li>
<li><p>Following best web practices</p>
</li>
</ul>
<p>You don’t need to configure accessibility – it just works :)</p>
<h2 id="heading-real-world-use-case-country-dropdown-with-flags">Real-World Use Case: Country Dropdown with Flags</h2>
<p>Looking for a more advanced dropdown? Here’s an amazing example that includes search, flag icons, and grouping:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752598285627/6cb8b27e-7cba-4d92-95c5-3bea44e0c01c.png" alt="Shadcn dropdown example" class="image--center mx-auto" width="879" height="483" loading="lazy"></p>
<p>👉 <a target="_blank" href="https://shadcn-country-dropdown.vercel.app/">shadcn-country-dropdown.vercel.app</a></p>
<p>It’s open-source and a great place to see what’s possible with shadcn/ui.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Using shadcn/ui to create a dropdown menu is fast, simple, and powerful. You get great styling, accessibility, and full control over how things look and work. Whether you’re just starting out or building for production, this is a solid tool to use.</p>
<p>Dropdowns are just the beginning. shadcn/ui offers a whole library of headless components for building modern UIs.</p>
<p>I hope you found this article helpful! If you're building a SaaS product or any web app that involves user interaction or conversion, consider enhancing user trust with real-time notifications like modal pop-ups, <a target="_blank" href="http://toastie.saasindie.com">sales pop up</a>, etc.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Tailwind CSS by Building a Responsive Product Card ]]>
                </title>
                <description>
                    <![CDATA[ Are you looking to enhance your web design skills and create responsive websites more efficiently? Tailwind CSS, a utility-first CSS framework, has become a favorite among developers for its flexibility and speed in building modern web layouts. One o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-tailwind-css-by-building-a-responsive-product-card/</link>
                <guid isPermaLink="false">670880854ae60bd6510c89c7</guid>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Fri, 11 Oct 2024 01:33:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728610417740/2c081996-9a92-42d5-bee1-a380f2a0c1e4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Are you looking to enhance your web design skills and create responsive websites more efficiently? Tailwind CSS, a utility-first CSS framework, has become a favorite among developers for its flexibility and speed in building modern web layouts. One of the most practical ways to master this framework is by applying it to real-world projects.</p>
<p>We just published a course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that will teach you all about building a responsive product card using Tailwind CSS. This course is designed for those who already have some foundational knowledge of Tailwind but are looking to explore more intermediate concepts. By the end, you'll be able to create a fully responsive, professional-grade product card for an e-commerce site, using best practices for both desktop and mobile layouts. Rachel Johnson from Scrimba created this course.</p>
<p>Throughout the course, you'll dive into key Tailwind CSS features and utilities, such as:</p>
<ul>
<li><p>Modifying the Tailwind config object to customize your design system.</p>
</li>
<li><p>Applying custom fonts, which allows you to bring unique typography to your project.</p>
</li>
<li><p>Controlling the maximum widths of elements for responsive layouts.</p>
</li>
<li><p>Utilizing Tailwind’s text utilities for precise control over font sizes, colors, and spacing.</p>
</li>
<li><p>Creating gradients and beautiful backgrounds using Tailwind’s gradient utilities.</p>
</li>
<li><p>Styling lists, building layouts with CSS Grid, and managing responsive designs with ease.</p>
</li>
<li><p>Adding background images, transitions, and transformations to make your design interactive and visually engaging.</p>
</li>
</ul>
<p>Each concept is broken down and explained step-by-step, making it easy to follow even if you’re newer to Tailwind. You’ll also explore how to use arbitrary values for custom styling, giving you even more creative control over your designs. By the end of this course, you’ll not only have a completed project but also a deeper understanding of how to apply Tailwind CSS in various web development scenarios, whether for personal projects or professional work.</p>
<p>This course is perfect for anyone looking to sharpen their skills in Tailwind CSS while building practical, real-world projects. Watch the full course <a target="_blank" href="https://youtu.be/cG2rf7hTvsw">on the freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/cG2rf7hTvsw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
