惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
B
Blog
博客园 - Franky
I
InfoQ
A
About on SuperTechFans
博客园_首页
L
LangChain Blog
量子位
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
美团技术团队
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
G
Google Developers Blog
Last Week in AI
Last Week in AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The Easiest Way to Implement Theme Toggling in React 19 u...
Ahmed Rabee · 2026-05-27 · via DEV Community

There is almost no React project today that doesn’t need at least two themes (Light and Dark). While you could build a custom theme toggle using the React Context API, it usually leads to complex boilerplate code just to handle system preferences and saving user choices.

Instead, we can use the incredibly popular NPM package next-themes. Despite the name, it works beautifully with plain React! While Next.js requires a bit more care with theming due to Server-Side Rendering (SSR) and hydration mismatches, setting this up in a React Single Page Application (SPA) is incredibly straightforward.

In this article, I will show you how to implement a seamless theme toggle using React 19, Vite, and the newly released Tailwind CSS v4.

Step 1: Create a New Vite Project

First things first, let’s create a new React project using Vite. Open your terminal and run:

pnpm create vite
# OR
npm create vite

Enter fullscreen mode Exit fullscreen mode

Follow the prompts to select React and JavaScript/TypeScript. Once that’s done, install and configure Tailwind CSS v4 according to their official documentation.

Step 2: Configure Tailwind CSS v4 for Dark Mode

Tailwind v4 is CSS-first, meaning we handle configuration right inside our CSS file. To enable class-based dark mode, we just need to define a custom variant.

Open your ./src/index.css file and set it up like this:

/* ./src/index.css */
@import "tailwindcss";
/* Add this line to enable class-based dark mode */
@custom-variant dark (&:where(.dark, .dark *));

Enter fullscreen mode Exit fullscreen mode

Step 3: Install Dependencies

Next, let’s install next-themes and a library for our theme icons (react-icons):

pnpm add next-themes react-icons
# OR
npm install next-themes react-icons

Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Theme Provider

Now, let’s create a wrapper component for our theme. This provider will wrap our entire application and inject the current theme into the HTML structure.

Create a new file called ReactThemeProvider.jsx:

// ./src/ReactThemeProvider.jsx
import { ThemeProvider } from "next-themes";
export default function ReactThemeProvider({ children }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </ThemeProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Note: By settingattribute="class" , we are tellingnext-themes to toggle thedark class on the<html> element, which works perfectly with our Tailwind configuration.

Step 5: Wrap Your App Component

Head over to your entry file and wrap the <App /> component with the provider we just created.

// ./src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import ReactThemeProvider from "./ReactThemeProvider.jsx";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    // wrap App component with ReactThemeProvider
    <ReactThemeProvider>
      <App />
    </ReactThemeProvider>
  </StrictMode>
);

Enter fullscreen mode Exit fullscreen mode

Step 6: Create the Toggle Button

Finally, let’s create the button that users will click to toggle the theme. We will use the useTheme hook provided by next-themes to check the resolvedTheme (which calculates whether the system preference is currently light or dark) and switch it.

// ./components/ToggleThemeBtn.jsx
import { useTheme } from "next-themes";
import { LuSun, LuMoon } from "react-icons/lu";

export function ToggleThemeBtn() {
  const { resolvedTheme, setTheme } = useTheme();
  return (
    <button
      onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
      className="p-2 rounded-md bg-slate-200 dark:bg-slate-800 text-slate-800 dark:text-slate-200 transition-colors"
      aria-label="Toggle theme"
    >
      {resolvedTheme === "dark" ? <LuSun size={20} /> : <LuMoon size={20} />}
    </button>
  );
}

Enter fullscreen mode Exit fullscreen mode

And that is it! You now have a fully functioning, Tailwind-compatible theme toggle that respects user system preferences and saves their choices locally. Easy, right?!