Skip to content

feat: add dark mode toggle with localStorage persistence#1109

Open
karkera-saakshi wants to merge 4 commits into
SB2318:webfrom
karkera-saakshi:feature/dark-mode-toggle
Open

feat: add dark mode toggle with localStorage persistence#1109
karkera-saakshi wants to merge 4 commits into
SB2318:webfrom
karkera-saakshi:feature/dark-mode-toggle

Conversation

@karkera-saakshi
Copy link
Copy Markdown

PR Description

Closes #930 Add Dark Mode Support for Better Visibility and User Experience

What's Changed

  • Installed and configured next-themes for dark mode support
  • Created ThemeProvider component following the shadcn/ui dark mode guide
  • Wrapped root layout with ThemeProvider with attribute="class", defaultTheme="system", and enableSystem
  • Added suppressHydrationWarning to tag to prevent FOUC (Flash of Unstyled Content)
  • Added sun/moon toggle button in the navbar
  • Theme preference is persisted in localStorage — refreshing the page keeps the selected theme
  • Full dark mode coverage across all pages and components including:
  1. Navbar
  2. Hero section
  3. Feature cards
  4. Screenshots section
  5. Contact section
  6. Input fields

Testing

  • Toggle works from the navbar on all pages
  • Theme persists after page refresh
  • No flash of unstyled content on load

Type of Change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update

Select your work-area

  • Frontend
  • Backend
  • Documentation
  • Others

Related Issue

Please provide a link to the issue solved if applicable.

Add your Work Example

Showcasing light and dark mode

darkmode.1.mp4

Reloading the page while in dark or light mode

reloading.mp4

Opening the same web in new tab to see it still ahs the same setting for modes as before

new.tab.mp4

Fixes (mention the issue number which this fixes)

#930

Checklist

  • I have updated my branch and synced it with the project's 'develop' branch before making this PR.
  • I have optimized the file changes.
  • I have added a snapshot of my work example.
  • I have made a PR to the project's develop branch.

Undertaking

  • My code follows the style guidelines of this project.

  • I have performed a self-review of my code.

  • I have commented my code, particularly in hard-to-understand areas.

  • I have made corresponding changes to the documentation.

  • I have checked for plagiarism and assure its authenticity.

  • I have read and followed the code of conduct for this repository. I understand that violation of this undertaking may have legal consequences.

  • I Agree

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Thank you @, for creating the PR and contributing to our UltimateHealth project 💗.
Our team will review the PR and will reach out to you soon! 😇
Make sure that you have marked all the tasks that you are done with ✅.
Thank you for your patience! 😀

Copy link
Copy Markdown
Owner

@SB2318 SB2318 left a comment

Choose a reason for hiding this comment

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

Thanks for your Contribution!

@SB2318
Copy link
Copy Markdown
Owner

SB2318 commented Jun 5, 2026

/review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

🤖 Gemini AI Code Review

Summary

This Pull Request introduces dark mode functionality to the application using next-themes and localStorage for persistence, aligning with the shadcn/ui dark mode guide. It includes a theme toggle in the navbar and aims for full dark mode coverage across various sections and components. The PR correctly implements ThemeProvider and suppressHydrationWarning to prevent FOUC.

While the intention and initial setup are good, the implementation suffers from several critical issues related to maintainability, potential regressions, and styling best practices, primarily an over-reliance on !important in the CSS.

🔴 High Severity

  • Issue: Metadata Loss in layout.tsx
    The metadata object in web/src/app/layout.tsx has had keywords and openGraph properties removed.

    --- a/web/src/app/layout.tsx
    +++ b/web/src/app/layout.tsx
    @@ -10,12 +10,7 @@
     
     export const metadata: Metadata = {
       title: "UltimateHealth - Empowering Wellness Through Global Community",
    -  description:
    -    "UltimateHealth is a platform that lets you publish health knowledge in your own language, review content, and share podcasts with the world.",
    -  keywords: "health, wellness, community, articles, podcasts, multilingual",
    -  openGraph: {
    -    title: "UltimateHealth",
    -    description: "Empowering Wellness Through Global Community",
    -    type: "website",
    -  },
    +  description: "UltimateHealth platform description.",
     };

    Impact: This is a significant regression for Search Engine Optimization (SEO) and social media sharing. Missing keywords can reduce organic search visibility, and the absence of Open Graph metadata will result in generic or incorrect previews when links to the site are shared on platforms like Facebook, Twitter, or LinkedIn.
    Fix: Revert the changes to the metadata object in web/src/app/layout.tsx to restore the description, keywords, and openGraph properties.

  • Issue: Potential withBasePath Regression for Links
    In web/src/app/page.tsx, the link for "Join Us to Contribute" has been changed from href={withBasePath("/contribute")} to href="/contribute".

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -961,7 +983,7 @@ export default function Home() {
             <a href="#programs">Programs</a>
             <a href="#screenshots">Screenshots</a>
             <a href="#contact">Contact</a>
    -        <a href={withBasePath("/contribute")}>Join Us &amp; Contribute</a>
    +        <a href="/contribute">Join Us & Contribute</a>
           </div>

    Impact: If the application is deployed to a subdirectory (e.g., https://example.com/my-app/), withBasePath is crucial for generating correct absolute paths. Removing it will cause this specific link to break, leading to a 404 error in production environments that rely on a base path.
    Fix: Revert the href attribute for the "Join Us to Contribute" link in web/src/app/page.tsx to use withBasePath("/contribute") to ensure correct path resolution in all deployment scenarios.

  • Issue: Excessive Use of !important in CSS
    Both web/src/app/globals.css and web/src/app/globals2.css are heavily littered with !important flags, particularly in the dark mode specific styles. This is evident in almost every new dark mode rule.

    /* Examples from diff */
    .dark body { background: var(--bg-white) !important; color: var(--text-dark) !important; }
    .dark .feature-card.mod-card:hover { background-color: rgba(255, 255, 255, 0.04) !important; }
    .dark .contact-section { background-color: hsl(222.2 84% 4.9%) !important; }

    Impact: Over-reliance on !important creates a highly rigid and unmaintainable stylesheet. It makes it extremely difficult to introduce new styles or modify existing ones without resorting to even more !important rules, leading to specificity wars, increased bundle size, and a poor developer experience. It indicates a fundamental issue with how styles are being applied or structured, often bypassing proper CSS specificity and cascade principles.
    Fix: This requires a significant refactor of the CSS.

    1. Leverage CSS Variables: Ensure all theme-related properties (colors, backgrounds, borders) are driven by CSS variables defined in the :root (light theme) and .dark (dark theme) selectors.
    2. Refactor Specificity: Instead of !important, use more specific selectors or ensure the theme variables are applied at the correct level. For example, if .feature-card.mod-card needs a background, it should use background-color: var(--color-surface-card); and var(--color-surface-card) should be defined in both light and dark theme blocks.
    3. Tailwind Integration: For components primarily styled with Tailwind, ensure the dark: prefix is used correctly with Tailwind classes, leveraging the darkMode: "class" configuration. For custom components, ensure they consume the CSS variables.
    4. Example (Conceptual):
      • Instead of:
        .dark .feature-card.mod-card {
          background-color: #1e293b !important;
          color: #ffffff !important;
        }
      • It should be:
        .feature-card.mod-card {
          background-color: var(--color-surface-card); /* Defined in :root and .dark */
          color: var(--color-foreground); /* Defined in :root and .dark */
        }
        /* In :root (light theme) */
        :root {
          --color-surface-card: #ffffff;
          --color-foreground: hsl(222.2 84% 4.9%);
        }
        /* In .dark (dark theme) */
        .dark {
          --color-surface-card: #1e293b;
          --color-foreground: hsl(210 40% 98%);
        }
      This allows the theme provider to switch the class, and the variables automatically update the styles without !important.

🟡 Medium Severity

  • Issue: Inconsistent CSS Variable Naming Conventions
    web/src/app/globals.css introduces dark mode variables like --text-dark, --bg-white, --shadow-sm, etc. In contrast, web/src/app/globals2.css uses a different, more shadcn/ui-like convention with --color-background, --color-foreground, --color-card, --color-surface-secondary, etc.
    Impact: This inconsistency makes the codebase harder to understand, maintain, and extend. Developers need to remember two different naming conventions for theme variables, increasing cognitive load and potential for errors.
    Fix: Standardize on a single, consistent naming convention for all CSS variables, preferably the --color- prefix used in globals2.css which aligns better with modern CSS practices and component libraries like shadcn/ui. Refactor the variables in globals.css accordingly.

  • Issue: Inline Styles for Layout Adjustment
    In web/src/app/page.tsx, an inline style style={{paddingTop: '80px'}} is added to the .hero-content div.

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -512,7 +529,7 @@ export default function Home() {
    
       {/* ── Hero ── */}
       <section className="hero">
    -    <div className="container hero-content scroll-reveal">
    +    <div className="container hero-content scroll-reveal" style={{paddingTop: '80px'}}>
           <h1>Empowering Wellness Through Global Community</h1>
           <p>UltimateHealth is a platform that lets you publish health knowledge in your own language, review content, and share podcasts with the world.</p>
         </PageWrapper>

    Impact: Inline styles bypass CSS stylesheets, making them difficult to override, track, and manage. They reduce component reusability and make it harder to maintain a consistent design system, especially across different themes or responsive breakpoints.
    Fix: Move this style into a dedicated CSS class (e.g., .hero-content-padded) in globals2.css or use a Tailwind utility class (e.g., pt-20 if 80px corresponds to Tailwind's p-20) directly on the element.

  • Issue: Mixed Styling Approaches for feature-card Hover State
    In web/src/app/page.tsx, the feature-card mod-card element has both custom CSS classes and inline Tailwind classes for its hover effect.

    --- a/web/src/app/page.tsx
    +++ b/web/src/app/page.tsx
    @@ -706,10 +723,13 @@ export default function Home() {
             ].map((f, i) => (
               <div 
    -          className="feature-card mod-card w-full fade-in" key={i}>
    +          className="feature-card mod-card w-full fade-in border-2 border-transparent transition-all duration-300 ease-in-out hover:border-[#8952c4] hover:bg-muted/40 dark:hover:bg-muted/10 cursor-pointer" 
    +          key={i}
    +        >
                 <div className="mod-icon"><i className={`fas ${f.icon}`}></i></div>
                 <h3>{f.title}</h3>
                 <p>{f.desc}</p>
               </div>
             ))}

    The globals2.css already defines hover states for .feature-card.mod-card for both light and dark modes.
    Impact: This creates a confusing and potentially conflicting styling strategy. It's unclear which styles take precedence, making debugging difficult and increasing the likelihood of unintended visual behavior. It also violates the principle of separation of concerns.
    Fix: Remove the inline Tailwind classes (border-2 border-transparent transition-all duration-300 ease-in-out hover:border-[#8952c4] hover:bg-muted/40 dark:hover:bg-muted/10 cursor-pointer) from the JSX. Ensure that all styling for .feature-card.mod-card, including its hover states, is consistently defined within globals2.css using CSS variables for theme adaptation.

  • Issue: Ambiguous Role of globals.css and globals2.css
    The project uses two global CSS files, web/src/app/globals.css and web/src/app/globals2.css, both of which are modified in this PR to include dark mode styles. The original layout.tsx comment // DO NOT remove the globals2.css import, it contains important global styles for the application suggests globals2.css is primary, yet globals.css is also present and modified.
    Impact: Having two global CSS files without clear documentation or separation of concerns can lead to confusion, redundant styles, and potential conflicts. Developers might not know where to add new global styles or debug existing ones.
    Fix: Consolidate all global styles into a single globals.css file. If there is a strong architectural reason to keep them separate (e.g., one for Tailwind base, one for custom components), this should be clearly documented, and their responsibilities strictly defined to avoid overlap and confusion.

🟢 Low Severity / Nits

  • Issue: Commented-out Code in theme-provider.tsx
    The web/src/components/theme-provider.tsx file contains a block of commented-out code.

    --- a/web/src/components/theme-provider.tsx
    +++ b/web/src/components/theme-provider.tsx
    @@ -0,0 +1,24 @@
    +// "use client"
    +//
    +// import * as React from "react"
    +// import { ThemeProvider as NextThemesProvider } from "next-themes"
    +//
    +// export function ThemeProvider({
    +//   children,
    +//   ...props
    +// }: React.ComponentProps<typeof NextThemesProvider>) {
    +//   return <NextThemesProvider {...props}>{children}</NextThemesProvider>
    +// }
    +
    +// src/components/theme-provider.tsx
    +"use client"
    +
    +import * as React from "react"
    import { ThemeProvider as NextThemesProvider } from "next-themes"
    +
    +export function ThemeProvider({
    +  children,
    +  ...props
    +}: React.ComponentProps<typeof NextThemesProvider>) {
    +  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
    +}

    Impact: Clutters the codebase and can be misleading.
    Fix: Remove the commented-out code block from web/src/components/theme-provider.tsx.

  • Issue: Missing Newline at End of File
    web/src/app/globals.css and web/src/app/globals2.css are missing a newline character at the end of the file.
    Impact: Minor, but can cause issues with some build tools, linters, or version control systems (e.g., diff tools might show an extra line change).
    Fix: Add a newline character at the very end of web/src/app/globals.css and web/src/app/globals2.css.

What's Good ✅

  1. Correct next-themes Integration: The PR correctly sets up next-themes by wrapping the root layout with ThemeProvider, configuring attribute="class", defaultTheme="system", and enableSystem. The ModeToggle component is also well-implemented, including the mounted state to prevent hydration mismatches.
  2. Effective FOUC Prevention: The inclusion of suppressHydrationWarning on the <html> tag is a crucial and correctly applied technique to prevent the "Flash of Unstyled Content" that often occurs with theme switching in Next.js applications.
  3. Improved Color System (HSL): The transition from oklch to hsl for color definitions in globals2.css is a positive step. HSL is generally more widely understood, supported, and easier to manipulate programmatically than oklch, aligning better with modern CSS practices and shadcn/ui conventions.

Verdict

Request Changes

The PR introduces several high-severity issues, most notably the loss of critical SEO/Open Graph metadata, a potential regression for base path-aware links, and a pervasive over-reliance on !important in the CSS, which severely impacts maintainability. These issues must be addressed before merging.

Copy link
Copy Markdown
Owner

@SB2318 SB2318 left a comment

Choose a reason for hiding this comment

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

🔴 High Severity

SEO Metadata Regression

The PR removes existing keywords and openGraph metadata from layout.tsx and replaces the detailed description with a generic placeholder.

This is a significant SEO and social-sharing regression that appears unrelated to the dark mode implementation.

Please restore the original metadata unless there is a documented reason for changing it.


Base Path Compatibility Regression

The "Join Us & Contribute" link has been changed from:

href={withBasePath("/contribute")}

to:

href="/contribute"

This may break deployments hosted under a custom base path. Please keep withBasePath() usage to ensure compatibility across deployment environments.

🟡 Medium Severity

Inconsistent Theme Variable Naming


Inline Style Added to Hero Section

The following inline style was introduced:

style={{ paddingTop: "80px" }}

Inline styles make maintenance and theme customization more difficult.

Please move this into a CSS class or Tailwind utility class.


Mixed Styling Approaches

feature-card now combines:

  • Custom CSS definitions
  • Tailwind hover classes

Styling for the same component is now split across multiple locations, making future maintenance harder.

Consider keeping hover behavior in a single place for consistency.


🟢 Low Severity

Remove Commented-Out Code

theme-provider.tsx contains an entire commented implementation above the active implementation.

Please remove unused commented code to keep the file clean.

✅ Positive Feedback

Nice work on the overall dark mode setup:

  • Proper integration of next-themes
  • Correct use of ThemeProvider
  • suppressHydrationWarning added to prevent hydration mismatches
  • Theme toggle implementation follows recommended patterns
  • System theme support is configured correctly

The dark mode foundation looks solid. The primary concerns are around maintainability, SEO regressions, and styling architecture that should be addressed before merging.

Verdict

Request Changes

The dark mode feature itself is a valuable addition, but the SEO metadata removal, base path regression risk, and extensive use of !important introduce maintainability and production concerns that should be resolved before merge.

@karkera-saakshi karkera-saakshi force-pushed the feature/dark-mode-toggle branch from 81a102f to 0db4694 Compare June 5, 2026 07:19
@karkera-saakshi
Copy link
Copy Markdown
Author

Hi @SB2318, I have addressed all the high severity issues:
✅ SEO metadata restored in layout.tsx
✅ withBasePath link restored in page.tsx
✅ Inline style moved to .hero-content-padded CSS class
✅ Removed Tailwind hover classes from feature-card — styling now consolidated in CSS
✅ Replaced !important with CSS variables where safe (feature-card, screenshot-details)
Regarding remaining !important flags — these are necessary to override existing styles in globals.css which uses hardcoded values. Removing them without refactoring globals.css would break the dark mode styling. I am happy to refactor globals.css in a follow-up PR if preferred.
Please let me know if further changes are needed!

@karkera-saakshi karkera-saakshi force-pushed the feature/dark-mode-toggle branch from 0db4694 to 945296b Compare June 5, 2026 07:25
@rushiii3
Copy link
Copy Markdown
Collaborator

rushiii3 commented Jun 6, 2026

@karkera-saakshi

You modified global2.css, which affects all UI components across the application. Please restore it to its original/default state.

If you want to support dark mode, there's no need to rewrite global styles or update every component's CSS. Tailwind already provides built-in dark mode utilities. You can simply add classes like:

className="bg-white dark:bg-black"

or

className="text-black dark:text-white"

and apply them only where needed. This keeps dark mode changes component-scoped and avoids introducing global styling changes that impact the entire UI.

Tailwind's recommended approach is to use the dark: variant rather than maintaining separate global CSS overrides.

Reference: https://tailwindcss.com/docs/dark-mode

@SB2318
Copy link
Copy Markdown
Owner

SB2318 commented Jun 6, 2026

@karkera-saakshi please consider @rushiii3 's Point, and please update. I am available.

@karkera-saakshi
Copy link
Copy Markdown
Author

Hi @SB2318 I am available and working on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants