🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 11 Min Lesezeit
0

Dark mode & light mode in next. Js 14 app router with material-ui without ui flickering

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Introduction



Dark mode has become a popular feature in modern web applications, enhancing user experience and adding a sleek, visually appealing touch. With Next.js 14 and Material-UI (MUI), you can seamlessly toggle between dark and light modes, giving users the flexibility to choose based on their preferences. One of the challenges, however, is ensuring that the UI doesn’t flicker during theme transitions due to Nextjs server side rendering.



In this article, we’ll implement a flicker-free dark mode and light mode in Next.js 14 using the new App Router, Material-UI, and MUI CSS variables to manage theme states smoothly.






Prerequisites



Before we start, ensure you have:





  • Node.js and npm installed.


  • Next.js 14 project setup.


  • Material-UI (MUI) for theme management.






Follow the steps below to install the necessary packages



Open your terminal and bootstrap a new nextjs project.



1.First, create a nextjs app




CODE
  npx [email protected] nextjs-mui-theme






Open the project in your coding environment



2.Install the MUI dependencies:




CODE
npm install @mui/material @emotion/react @emotion/styled next-themes






3.Install MUI library for nextjs




CODE
npm install @mui/material-nextjs/v15-appRouter






4.Install Roboto fonts




CODE
npm install @fontsource/roboto






5.Copy & paste the code below in src/app/layout.tsx and




CODE
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';






6.Install MUI icons library




CODE
npm install @mui/icons-material






7.Copy the code below and replace the code in the file src/app/globals.css




CODE

@import url(https://fonts.googleapis.com/icon?family=Material+Icons);

html,
body {
max-width: 100vw;
overflow-x: hidden;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
}







8.Install the package for generating dummy data




CODE
  npm i @faker-js/faker






If you did the above, your package.json should look like this




CODE
{
"name": "nextjs-mui-theme",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/cache": "^11.13.1",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@faker-js/faker": "^9.2.0",
"@fontsource/roboto": "^5.1.0",
"@mui/icons-material": "^6.1.6",
"@mui/material": "^6.1.6",
"@mui/material-nextjs": "^6.1.6",
"next": "14.2.16",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.16",
"typescript": "^5"
}
}







9.Create two directories in src directory




  • context

  • components





  1. Open context driectory and create a file AppThemeContext.tsx and paste the following code.



    To handle theme switching, we’ll create a custom ThemeProvider component. This component will wrap our application, detect the theme preference (dark or light), and apply it without causing UI flickering.






CODE
'use client'
import { createTheme, CssBaseline, responsiveFontSizes, ThemeProvider } from "@mui/material";
import { createContext, useContext, useMemo } from "react";
import type {} from '@mui/material/themeCssVarsAugmentation';


const AppThemeContext = createContext(null)


const AppThemeProvider = (props: any) => {

const theme = useMemo(() => {
return responsiveFontSizes(createTheme({
cssVariables: {
colorSchemeSelector: "class",
disableCssColorScheme: true
},
palette: {
primary: {
main: `rgb(10, 18, 42)`,
contrastText: 'rgb(255, 255, 255)',
},
secondary: {
main: `rgb(27, 59, 111)`,
contrastText: 'rgb(255, 255, 255)',
}
},
colorSchemes: {
light: {
palette: {
primary: {
main: `rgb(10, 18, 42)`,
},
secondary: {
main: `rgb(27, 59, 111)`,
}
}
},
dark: {
palette: {
primary: {
main: `rgb(10, 18, 42)`,
},
secondary: {
main: `rgb(27, 59, 111)`,
}
}
}
}
}))
}, [])

return <AppThemeContext.Provider value={null}>
<ThemeProvider theme={theme} disableTransitionOnChange>
<CssBaseline enableColorScheme />
{props.children}
</ThemeProvider>
</AppThemeContext.Provider>
}

export const useAppThemeContext = () => useContext(AppThemeContext)

export default AppThemeProvider






Explanation




  • We created a theme object to be used throughout our application


  • Code line 14 - 17, specifies the cssVariables config which enables css variables in MUI


  • We defined the palette for our app


  • We defined the colorSchemes which specifies our light and dark mode themes. This can be modified based on your needs.


  • ThemeProvider: This provider applies the selected MUI theme (either dark or light) to the entire app. You





Notice that we passed theme object to the ThemeProvider component





  • We called CssBaseline from mui with the prop enableColorScheme which will enable color schemes based on user selection or system default and reset the proper styles for our app to display well on different devices.


  • We exported our custom AppThemeProvider which will wrap the whole of our application.




11.Open src/app/layout.tsx file and replace the following code




CODE
import type { Metadata } from "next";
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import "./globals.css";
import InitColorSchemeScript from "@mui/material/InitColorSchemeScript"
import AppThemeProvider from "@/context/AppThemeContext";
import {MainNavbar} from "@/components"

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<AppRouterCacheProvider options={{enableCssLayer: false}}>
<AppThemeProvider>
<InitColorSchemeScript attribute="class" />
<MainNavbar />
{children}
</AppThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}







Explanation




  • We called AppRouterCacheProvider from mui nextjs library and wrap our children inside the body element.

    The component also accept an option of enableCssLayer: false


  • We imported our custom AppThemeProvider to wrap our whole app to apply our MUI styles.


  • We called <InitiColorSchemeScript attribute="class" /> to initialize our default mode on the server without a flicker.

    Since cssVariables is enabled and initialized, this can be applied on the on server without a flicker.





Remember the attribute="class" must be the same value with the custom theme cssVariables config in AppThemeContext.tsx file cssVariables: { colorSchemeSelector: "class" }





  • Since we want our navbar to persist across pages, we called <MainNavbar /> and then {children} which will be any component passed to the layout in this case the page.tsx component.



12.Open src/components directory and create 4 files namely;

- index.tsx

- MainNavbar.tsx

- Homepage.tsx

- MediaCard.tsx




Open src/components/index.tsx and paste the code below;







CODE
export { default as MainNavbar } from "./MainNavbar"

export { default as Homepage } from "./Homepage"








Open src/components/MainNavbar.tsx and paste the code below;





CODE
'use client'
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import MenuIcon from '@mui/icons-material/Menu';
import Container from '@mui/material/Container';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem';
import AdbIcon from '@mui/icons-material/Adb';
import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined';
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined';
import { useColorScheme } from '@mui/material/styles';


const pages = ['Products', 'Pricing', 'Blog'];
const settings = ['Profile', 'Account', 'Dashboard', 'Logout'];

function MainNavbar() {
const { mode, systemMode, setMode } = useColorScheme();
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);

const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUser(event.currentTarget);
};

const handleCloseNavMenu = () => {
setAnchorElNav(null);
};

const handleCloseUserMenu = () => {
setAnchorElUser(null);
};

const toggleDarkTheme = React.useCallback(() => {
if(mode){
const currMode = mode === 'dark' ? 'light' : 'dark';
setMode(currMode);
}
},[mode, systemMode])

return (
<AppBar position="fixed">
<Container maxWidth="xl">
<Toolbar disableGutters>
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
<Typography
variant="h6"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography>

<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{ display: { xs: 'block', md: 'none' } }}
>
{pages.map((page) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Typography sx={{ textAlign: 'center' }}>{page}</Typography>
</MenuItem>
))}
</Menu>
</Box>
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button
key={page}
onClick={handleCloseNavMenu}
sx={{ my: 2, color: 'white', display: 'block' }}
>
{page}
</Button>
))}
</Box>


<Box sx={{ flexGrow: 0, pr: 2 }}>
<Tooltip title="Toggle Theme">
<IconButton size='large' color='inherit' onClick={() => toggleDarkTheme()} sx={{ p: 0 }}>
{
mode === "dark" ? <DarkModeOutlinedIcon /> : <LightModeOutlinedIcon />
}
</IconButton>
</Tooltip>
</Box>


<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{settings.map((setting) => (
<MenuItem key={setting} onClick={handleCloseUserMenu}>
<Typography sx={{ textAlign: 'center' }}>{setting}</Typography>
</MenuItem>
))}
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
);
}
export default MainNavbar;







Explanation




  • To implement theme toggle, we import useColorScheme on line 18 from @mui/material/styles


  • We called it on line 25 to get the mode and systemMode


  • We created a function toggleDarkTheme on line 44 to 49 to check if mode is set theme toggle the opposite then we called it on line 142





Open src/components/Homepage.tsx and paste the code below;





CODE
'use client'
import React from 'react'
import { faker } from '@faker-js/faker';
import { Box, Container, Grid2 } from '@mui/material';
import MediaCard from './MediaCard';

const Homepage = () => {
const data = Array.from({ length: 24 }).map(() => ({
id: faker.string.uuid(),
title: faker.lorem.lines(1),
content: faker.lorem.sentences(4),
src: faker.image.urlPicsumPhotos()
}))
return (
<Box sx={[(theme) => ({
backgroundColor: theme.vars.palette.grey[300],
...theme.applyStyles("dark", {
backgroundColor: theme.vars.palette.grey[900]
})
})]}

>
<Container maxWidth="xl" sx={{mt: 9, pt: 2}}>
<Grid2 container spacing={2}>
{data.map(item => (
<Grid2 key={item.id} size={{lg: 3, md: 4, sm: 12, xs: 12}}>
<MediaCard item={item} />
</Grid2>
))}
</Grid2>
</Container>
</Box>
)
}

export default Homepage







Open src/components/MediaCard.tsx and paste the code below;





CODE
import * as React from 'react';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

type Item = { id: string, title: string, src: string, content: string}

export default function MediaCard({item}: { item: Item}) {
return (
<Card sx={{ height: "100%"}}>
<CardMedia
sx={{ height: 140 }}
image={item.src}
title={item.title}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{item.title}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.content}
</Typography>
</CardContent>
<CardActions sx={{justifyContent: "space-between"}}>
<Button color='inherit' variant='outlined' size="small">Share</Button>
<Button color='inherit' variant='outlined' size="small">Learn More</Button>
</CardActions>
</Card>
);
}







13.Open src/app/page.tsx file and paste the following code




CODE

import { Homepage } from "@/components";

export default function Home() {
return ( <Homepage />);
}







14.Open your terminal within vs code or your chosen environment and run the command the blow to start the nextjs project.




CODE
npm run dev






The above command should start a nextjs server listening on port 3000 like;



http://localhost:3000



Go ahead and open it in the browser and navigate to the above url and you should see your project running!



With the above implementation you should be able to toggle between light and dark mode without UI flickering.






Minimize Flickering



Using ThemeProvider’s defaultMode="system" and InitColorSchemeScript's defaultMode="system" ensures that the app initially matches the user’s system theme on load. This approach minimizes the risk of a flicker effect and provides a smoother experience. If the above are not provided, the system's default mode will picked by default.






Conclusion



You’ve successfully implemented dark mode and light mode in your Next.js 14 app using MUI without flickering. Here are some additional tips for enhancing your implementation:





  • System Theme Detection: Mui ThemeProvider’ defaultTheme="system" applies the system’s theme by default, ensuring that users start with their preferred theme.


  • Server-Side Rendering: If using SSR, this implementation handles theme detection automatically to avoid flickering.


  • Customization: You can further customize light and dark in AppThemeContext.tsx for brand-specific colors, fonts, and more!



By following these steps, you can provide a smooth, flicker-free dark mode experience in your Next.js app, enhancing both usability and visual appeal.






Get The Source Code



For the full implementation, you can find the source code here Nextjs Mui Theme Code






Youtube Video



A video is worth a thousand words!





Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Dark mode & light mode in next. Js 14 app router with material-ui without ui flickering

Thematisch verwandte Begriffe: Dark, mode, light, next · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...