feat: Add dark mode support with toggle button in navbar#362
feat: Add dark mode support with toggle button in navbar#362RishiMaskare wants to merge 2 commits intoRohanExploit:mainfrom
Conversation
- 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
👷 Deploy request for fixmybharat pending review.Visit the deploys page to approve it
|
🙏 Thank you for your contribution, @RishiMaskare!PR Details:
Quality Checklist:
Review Process:
Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken. |
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
👋 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! 🚀
|
Done Implementing dark mode!! @RohanExploit |
There was a problem hiding this comment.
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-colorwon't animate gradient switches.The
#rootelement usesbackgroundwith alinear-gradient(which is abackground-image), notbackground-color. CSS cannot interpolate between gradient values, so neithertransition: background-colornortransition: backgroundwill 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: Conflictingblockandflexdisplay classes.This element has both
blockandflexin the className.flexoverridesblock, making it redundant. Removeblockfor 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.jsxandAppHeader.jsx. Extracting it into a reusableDarkModeTogglecomponent would reduce duplication and ensure consistent behavior/styling.
| <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> |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
| function AppContent() { | ||
| const navigate = useNavigate(); | ||
| const location = useLocation(); | ||
| const { isDarkMode } = useDarkMode(); |
There was a problem hiding this comment.
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>
- 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
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
DarkModeContext (
frontend/src/contexts/DarkModeContext.jsx)localStoragewith keydarkModedarkclass to document root for Tailwind CSS dark modeDark Mode Toggle Button (
frontend/src/components/AppHeader.jsx)CSS Theme Styles (
frontend/src/App.css)#rootelementKey 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
Test Toggle Functionality:
Test Persistence:
Test Components:
Screenshots
Light Mode:
Dark Mode:
Technical Details
App.jsxdarkMode(JSON boolean):root.darkselector for responsive stylingclassmode enabled in configBrowser Compatibility
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:prefixScreenshots:



Summary by cubic
Adds full dark mode support with a theme toggle in the navbar. Preferences persist across sessions and the UI transitions smoothly.
Written for commit 88fd225. Summary will update on new commits.
Summary by CodeRabbit