Skip to content

feat: Add dark mode support with toggle button in navbar#362

Open
RishiMaskare wants to merge 2 commits intoRohanExploit:mainfrom
RishiMaskare:feature/dark-mode-toggle
Open

feat: Add dark mode support with toggle button in navbar#362
RishiMaskare wants to merge 2 commits intoRohanExploit:mainfrom
RishiMaskare:feature/dark-mode-toggle

Conversation

@RishiMaskare
Copy link

@RishiMaskare RishiMaskare commented Feb 9, 2026

Description

This PR implements dark mode support with a toggle button in the navbar, resolving issue #329.

Closes #329

Changes Made

Frontend Dark Mode Implementation

  1. DarkModeContext (frontend/src/contexts/DarkModeContext.jsx)

    • Created a React Context to manage dark mode state globally
    • Persists user preference to localStorage with key darkMode
    • Applies/removes dark class to document root for Tailwind CSS dark mode
    • Initial state respects system preference (defaults to light mode)
  2. Dark Mode Toggle Button (frontend/src/components/AppHeader.jsx)

    • Added Sun/Moon icons (from lucide-react) in the navbar
    • Toggle button switches between light and dark themes
    • Visual feedback: different icons for each mode
      • 🌙 Moon icon in light mode
      • ☀️ Sun icon in dark mode
  3. CSS Theme Styles (frontend/src/App.css)

    • Added dark mode background gradients for #root element
    • Added dark mode styles for animated background effects
    • Smooth transitions (0.3s) between light and dark modes
    • Reduced opacity on dark mode gradients for better contrast

Key Features

Theme Persistence - User preference saved in localStorage across sessions
Smooth Transitions - CSS transitions make mode switching fluid
Tailwind Dark Mode - Uses Tailwind's dark: utilities for component styling
Navbar Integration - Easy-access toggle button in the header
Accessibility - Proper visual indicators for current theme

Testing Instructions

  1. Test Toggle Functionality:

    • Click the Sun/Moon icon in the navbar header
    • Verify the theme switches between light and dark instantly
    • Check that all components properly display in both modes
  2. Test Persistence:

    • Switch to dark mode
    • Reload the page (F5)
    • Verify dark mode remains active
  3. Test Components:

    • Verify navbar backgrounds/text colors are appropriate in each mode
    • Check animated background effects render correctly in both modes
    • Test all detector pages and views display properly

Screenshots

Light Mode:

  • Navbar displays with light background
  • Moon icon visible (indicating dark mode is available)

Dark Mode:

  • Navbar displays with dark gray background
  • Sun icon visible (indicating light mode is available)
  • All page backgrounds adapt to dark theme

Technical Details

  • Context Provider: Wraps entire app in App.jsx
  • Storage Key: darkMode (JSON boolean)
  • CSS Strategy: :root.dark selector for responsive styling
  • Framework: Tailwind CSS with class mode enabled in config

Browser Compatibility

  • ✅ Chrome/Edge (latest)
  • ✅ Firefox (latest)
  • ✅ Safari (latest)
  • ✅ Mobile browsers

Files Modified

  • frontend/src/contexts/DarkModeContext.jsx (created)
  • frontend/src/components/AppHeader.jsx (modified)
  • frontend/src/App.css (modified)
  • frontend/src/App.jsx (already imported context)

Notes

  • Dark mode is now available on all pages and components that use Tailwind's dark: prefix
  • The toggle is always visible and accessible from any page
  • No breaking changes - light mode remains the default

Screenshots:
image
image
image


Summary by cubic

Adds full dark mode support with a theme toggle in the navbar. Preferences persist across sessions and the UI transitions smoothly.

  • New Features
    • Global DarkModeContext sets the document “dark” class and saves preference to localStorage.
    • Sun/Moon toggle added to AppHeader and Landing header.
    • Dark variants for backgrounds, text, menus, and animated effects across key views.
    • Tailwind dark mode enabled (class strategy) and app wrapped with DarkModeProvider.

Written for commit 88fd225. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Dark mode now available with a convenient toggle button in the header
    • Your theme preference is automatically saved and restored on future visits
    • Smooth, animated transitions when switching between light and dark themes
    • Entire interface including header, cards, text, and backgrounds fully themed for dark mode

- Create DarkModeContext for managing dark mode state
- Add dark mode toggle button (Sun/Moon icon) to AppHeader and Landing page navbar
- Default to light mode, toggle between light and dark modes
- Apply dark mode styling to all components using Tailwind dark: utilities
- Enable dark mode in Tailwind configuration
- Store dark mode preference in localStorage for persistence
- Support smooth color transitions between modes
@netlify
Copy link

netlify bot commented Feb 9, 2026

👷 Deploy request for fixmybharat pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 88fd225

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🙏 Thank you for your contribution, @RishiMaskare!

PR Details:

Quality Checklist:
Please ensure your PR meets the following criteria:

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is commented where necessary
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests added/updated (if applicable)
  • All tests passing locally
  • No breaking changes to existing functionality

Review Process:

  1. Automated checks will run on your code
  2. A maintainer will review your changes
  3. Address any requested changes promptly
  4. Once approved, your PR will be merged! 🎉

Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken.

@github-actions github-actions bot added the size/m label Feb 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This PR implements dark mode functionality across the frontend by introducing a context-based state management system using localStorage for persistence, applying Tailwind CSS class-based dark mode configuration, and adding dark-mode-aware styling to UI components including a toggle button in the header.

Changes

Cohort / File(s) Summary
Dark Mode Infrastructure
frontend/src/contexts/DarkModeContext.jsx, frontend/tailwind.config.js
Creates new context module for managing dark mode state with localStorage persistence and provides useDarkMode hook; enables Tailwind dark mode via CSS class strategy.
App Integration
frontend/src/App.css, frontend/src/App.jsx
Adds dark mode styling with color and background transitions; wraps app with DarkModeProvider and integrates useDarkMode hook for conditional theme application.
Component Styling
frontend/src/components/AppHeader.jsx, frontend/src/views/Landing.jsx
Adds dark mode toggle button with Sun/Moon icons in header; applies extensive dark-mode conditional styling (background, text, borders, gradients) across header user menu, landing container, and decorative elements.

Sequence Diagram

sequenceDiagram
    participant User
    participant Toggle as Dark Mode Toggle
    participant Context as DarkModeContext
    participant Storage as localStorage
    participant DOM as Document Root
    participant CSS as CSS Engine

    User->>Toggle: Click toggle button
    Toggle->>Context: Call toggleDarkMode()
    Context->>Context: Update isDarkMode state
    Context->>Storage: Persist isDarkMode to localStorage
    Context->>DOM: Add/remove 'dark' class
    DOM->>CSS: Apply dark mode stylesheet rules
    CSS->>User: Render dark/light theme
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🌙 Through localStorage's loyal keep,
Dark mode sleeps where preferences sleep,
A toggle dances, Sun meets Moon,
Tailwind classes switch in tune—
Light and shadow, hand in hand!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding dark mode support with a toggle button in the navbar, which is the primary objective of this pull request.
Linked Issues check ✅ Passed All requirements from issue #329 are met: dark mode toggle added to navbar, localStorage persistence implemented, system preference consideration in context, and frontend-only changes applied.
Out of Scope Changes check ✅ Passed All changes are within scope and directly support dark mode implementation: context creation, component styling updates, Tailwind config changes, and navbar integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Hello @RishiMaskare! Thank you for your first Pull Request to VishwaGuru! 🎉

We appreciate your contribution to this open source platform empowering India's youth to engage with democracy.

What happens next?

  • A maintainer will review your PR soon
  • Please ensure all tests pass and the code follows our style guidelines
  • Be ready to address any feedback or requested changes

Before your PR is merged:

  • Ensure your code follows the project's coding standards
  • All tests should pass
  • Update documentation if needed
  • Make sure your commits are clear and descriptive

Resources:

Thank you for contributing! 🚀

@RishiMaskare
Copy link
Author

Done Implementing dark mode!! @RohanExploit

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@frontend/src/components/AppHeader.jsx`:
- Around line 35-45: The toggle button currently only sets a title and lacks an
accessible label; update the button element that uses toggleDarkMode and
isDarkMode to include an appropriate aria-label (e.g., aria-label={isDarkMode ?
'Switch to light mode' : 'Switch to dark mode'}) so screen readers receive the
same descriptive text as the visual tooltip while keeping the existing title and
icon logic intact.

In `@frontend/src/contexts/DarkModeContext.jsx`:
- Around line 6-13: The useState initializer for isDarkMode currently defaults
to false when no localStorage value exists and should instead respect the system
preference; update the initializer in DarkModeContext.jsx (where isDarkMode and
setIsDarkMode are created via useState) to check for
window.matchMedia('(prefers-color-scheme: dark)').matches (guarding against SSR
by ensuring window and matchMedia exist) and return true when the media query
indicates dark mode, otherwise return false; keep the existing localStorage
parse logic but only fall back to the prefers-color-scheme result when savedMode
is null.

In `@frontend/src/views/Landing.jsx`:
- Around line 146-156: The dark-mode toggle button in Landing.jsx is missing an
accessibility label; update the button element used with toggleDarkMode (the one
rendering Sun/Moon based on isDarkMode) to include an aria-label prop that
mirrors the title (e.g., aria-label={isDarkMode ? 'Switch to light mode' :
'Switch to dark mode'}) so screen readers receive the same descriptive text as
AppHeader's toggle.
🧹 Nitpick comments (3)
frontend/src/App.css (1)

29-35: transition: background-color won't animate gradient switches.

The #root element uses background with a linear-gradient (which is a background-image), not background-color. CSS cannot interpolate between gradient values, so neither transition: background-color nor transition: background will produce a smooth fade between the light and dark gradients — the switch will be instant regardless. The transition property here is misleading; consider removing it or switching to an opacity-based crossfade technique if a smooth transition is desired.

frontend/src/components/AppHeader.jsx (1)

59-59: Conflicting block and flex display classes.

This element has both block and flex in the className. flex overrides block, making it redundant. Remove block for clarity.

Proposed fix
-                      <button onClick={handleLogout} className="block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 transition-colors duration-200">
+                      <button onClick={handleLogout} className="flex w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 items-center gap-2 transition-colors duration-200">
frontend/src/views/Landing.jsx (1)

145-156: Consider extracting the dark mode toggle into a shared component.

The toggle button markup (icon selection, click handler, styling, title) is duplicated between Landing.jsx and AppHeader.jsx. Extracting it into a reusable DarkModeToggle component would reduce duplication and ensure consistent behavior/styling.

Comment on lines +35 to +45
<button
onClick={toggleDarkMode}
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDarkMode ? (
<Sun size={20} className="text-yellow-400" />
) : (
<Moon size={20} className="text-gray-700" />
)}
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add aria-label for screen reader accessibility.

The toggle button uses title for a tooltip, but screen readers primarily rely on aria-label. Without it, the button is announced without a meaningful label.

Proposed fix
             <button
               onClick={toggleDarkMode}
               className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
               title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
+              aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
             >
🤖 Prompt for AI Agents
In `@frontend/src/components/AppHeader.jsx` around lines 35 - 45, The toggle
button currently only sets a title and lacks an accessible label; update the
button element that uses toggleDarkMode and isDarkMode to include an appropriate
aria-label (e.g., aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to
dark mode'}) so screen readers receive the same descriptive text as the visual
tooltip while keeping the existing title and icon logic intact.

Comment on lines +6 to +13
const [isDarkMode, setIsDarkMode] = useState(() => {
// Initialize from localStorage, default to light mode (false)
const savedMode = localStorage.getItem('darkMode');
if (savedMode !== null) {
return JSON.parse(savedMode);
}
// Default to light mode
return false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing system preference detection (prefers-color-scheme).

The PR objectives and linked issue #329 explicitly require: "Default theme should respect system preference via prefers-color-scheme." Currently, when no localStorage value exists, the code defaults to false (light mode), ignoring the user's OS-level preference.

Proposed fix
   const [isDarkMode, setIsDarkMode] = useState(() => {
     const savedMode = localStorage.getItem('darkMode');
     if (savedMode !== null) {
-      return JSON.parse(savedMode);
+      try {
+        return JSON.parse(savedMode);
+      } catch {
+        return false;
+      }
     }
-    // Default to light mode
-    return false;
+    // Respect system preference
+    return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [isDarkMode, setIsDarkMode] = useState(() => {
// Initialize from localStorage, default to light mode (false)
const savedMode = localStorage.getItem('darkMode');
if (savedMode !== null) {
return JSON.parse(savedMode);
}
// Default to light mode
return false;
const [isDarkMode, setIsDarkMode] = useState(() => {
// Initialize from localStorage, default to light mode (false)
const savedMode = localStorage.getItem('darkMode');
if (savedMode !== null) {
try {
return JSON.parse(savedMode);
} catch {
return false;
}
}
// Respect system preference
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
🤖 Prompt for AI Agents
In `@frontend/src/contexts/DarkModeContext.jsx` around lines 6 - 13, The useState
initializer for isDarkMode currently defaults to false when no localStorage
value exists and should instead respect the system preference; update the
initializer in DarkModeContext.jsx (where isDarkMode and setIsDarkMode are
created via useState) to check for window.matchMedia('(prefers-color-scheme:
dark)').matches (guarding against SSR by ensuring window and matchMedia exist)
and return true when the media query indicates dark mode, otherwise return
false; keep the existing localStorage parse logic but only fall back to the
prefers-color-scheme result when savedMode is null.

Comment on lines +146 to +156
<button
onClick={toggleDarkMode}
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDarkMode ? (
<Sun size={20} className="text-yellow-400" />
) : (
<Moon size={20} className="text-gray-700" />
)}
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add aria-label for the toggle button (same as AppHeader).

Same accessibility gap as in AppHeader.jsx — the dark mode toggle here also needs an aria-label for screen readers.

Proposed fix
                         <button
                             onClick={toggleDarkMode}
                             className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
                             title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
+                            aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
                         >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={toggleDarkMode}
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDarkMode ? (
<Sun size={20} className="text-yellow-400" />
) : (
<Moon size={20} className="text-gray-700" />
)}
</button>
<button
onClick={toggleDarkMode}
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDarkMode ? (
<Sun size={20} className="text-yellow-400" />
) : (
<Moon size={20} className="text-gray-700" />
)}
</button>
🤖 Prompt for AI Agents
In `@frontend/src/views/Landing.jsx` around lines 146 - 156, The dark-mode toggle
button in Landing.jsx is missing an accessibility label; update the button
element used with toggleDarkMode (the one rendering Sun/Moon based on
isDarkMode) to include an aria-label prop that mirrors the title (e.g.,
aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}) so
screen readers receive the same descriptive text as AppHeader's toggle.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/src/contexts/DarkModeContext.jsx">

<violation number="1" location="frontend/src/contexts/DarkModeContext.jsx:9">
P2: Guard JSON.parse when reading `darkMode` from localStorage to avoid crashing on malformed values.</violation>
</file>

<file name="frontend/src/App.jsx">

<violation number="1" location="frontend/src/App.jsx:53">
P3: Remove the unused `isDarkMode` destructuring or use it; leaving it unused will fail linting and adds dead code.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const [isDarkMode, setIsDarkMode] = useState(() => {
// Initialize from localStorage, default to light mode (false)
const savedMode = localStorage.getItem('darkMode');
if (savedMode !== null) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard JSON.parse when reading darkMode from localStorage to avoid crashing on malformed values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/contexts/DarkModeContext.jsx, line 9:

<comment>Guard JSON.parse when reading `darkMode` from localStorage to avoid crashing on malformed values.</comment>

<file context>
@@ -0,0 +1,44 @@
+  const [isDarkMode, setIsDarkMode] = useState(() => {
+    // Initialize from localStorage, default to light mode (false)
+    const savedMode = localStorage.getItem('darkMode');
+    if (savedMode !== null) {
+      return JSON.parse(savedMode);
+    }
</file context>
Fix with Cubic

function AppContent() {
const navigate = useNavigate();
const location = useLocation();
const { isDarkMode } = useDarkMode();
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Remove the unused isDarkMode destructuring or use it; leaving it unused will fail linting and adds dead code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/App.jsx, line 53:

<comment>Remove the unused `isDarkMode` destructuring or use it; leaving it unused will fail linting and adds dead code.</comment>

<file context>
@@ -49,6 +50,7 @@ import AdminDashboard from './views/AdminDashboard';
 function AppContent() {
   const navigate = useNavigate();
   const location = useLocation();
+  const { isDarkMode } = useDarkMode();
   const [responsibilityMap, setResponsibilityMap] = useState(null);
   const [actionPlan, setActionPlan] = useState(null);
</file context>
Fix with Cubic

- Add dark mode styles for #root element background
- Add dark mode styles for animated background effects
- Ensure smooth transition between light and dark themes with CSS
- Dark mode respects the 'dark' class on :root element added by DarkModeContext
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add dark mode support with toggle in navbar

1 participant