DEV Community

Cover image for The Simplest Way to Add Sass Themes to Your Next.js Project
Petromir Petrov
Petromir Petrov

Posted on

2 1 1 1 1

The Simplest Way to Add Sass Themes to Your Next.js Project

Challenges to Overcome

While Sass is powerful for writing cleaner, more modular CSS, it has limitations for runtime theming, as Sass code is compiled before the browser renders it. This means that pure Sass can't dynamically switch themes at runtime. However, by combining CSS variables with Sass, we can achieve runtime theming with minimal extra effort.

Setting Up a Theme Context in Next.js

The context provider will manage the theme state across components. The example code here is written for a Next.js 14 project, but it's adaptable for any React setup.

// ThemeProvider.tsx
'use client'
import React, { createContext, FC, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
import './ThemeProvider.scss';

type Theme = 'dark' | 'light';

type ContextReturnType = {
    theme: Theme;
    toggleTheme: () => void;
};

type Props = {
    children: React.ReactNode;
};

const ThemeContext = createContext<ContextReturnType>({
    theme: 'dark',
    toggleTheme: () => {},
});

const ThemeProvider: FC<Props> = ({ children }) => {
    const [theme, setTheme] = useState<Theme>(() => {
        if (typeof window !== 'undefined') {
            const savedTheme = localStorage.getItem('theme');
            return (savedTheme || 'dark') as Theme;
        }
        return 'dark';
    });

    const [isLoading, setIsLoading] = useState(true);

    const toggleTheme = useCallback(() => {
        setTheme(prevTheme => {
            const nextTheme = prevTheme === 'light' ? 'dark' : 'light';
            localStorage.setItem('theme', nextTheme);
            return nextTheme;
        });
    }, []);

    useLayoutEffect(() => {
        document.documentElement.className = theme;
        setIsLoading(false);
    }, [theme]);

    const contextValue = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);

    if (isLoading) {
        return null;
    }

    return <ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>;
};

export const useThemeContext = () => useContext(ThemeContext);
export default ThemeProvider;
Enter fullscreen mode Exit fullscreen mode

The theme is stored in the state and saved to localStorage so the user's preference persists across sessions. The toggleTheme function switches between light and dark themes and updates localStorage. We use useLayoutEffect to apply the theme class to the HTML element, enabling us to style based on this class in Sass.

Defining the Theme with Sass

Next, let's define our theme styles using Sass. We'll create two mixins, Text and Background, to handle colors for text and background but you can have as many as you need.

// theme.scss
@mixin Text($primary, $secondary) {
    --text-primary: #{$primary};
    --text-secondary: #{$secondary};
}

@mixin Background($primary, $secondary) {
    --background-primary: #{$primary};
    --background-secondary: #{$secondary};
}

.dark {
    --theme: dark;
    @include Text('#F9F9F9', '#C6C6C6');
    @include Background('#0D0E10', '#1E1F24');
}

.light {
    --theme: light;
    @include Text('#1C1C1C', '#4A4A4A');
    @include Background('#FFFFFF', '#f2f7ff');
}
Enter fullscreen mode Exit fullscreen mode

The Text and Background mixins allow us to easily apply color schemes for each theme. The .dark and .light classes define CSS variables for each theme. The --theme property lets us identify which theme is active within Sass styles. You can use that to apply theme-specific styles.

Using CSS Variables in Sass

To make our theme values available in Sass, let's create a helper function that converts CSS variables into Sass variables.

// mixins.scss
@function theme($color-name) {
    @return var(--#{$color-name});
}
Enter fullscreen mode Exit fullscreen mode

The theme function retrieves CSS variable values by their name, allowing you to use theme('text-primary') in any Sass file where you want to access theme-dependent styles.

Applying Themes to Components

Now, let's apply these theme variables to a component. Here's how to style a button component with theme-dependent colors.

// Button.module.scss
@import 'mixins.scss';

.button {
    color: theme('text-primary');
    background-color: theme('background-primary');
}
Enter fullscreen mode Exit fullscreen mode

The Button component now uses theme-specific colors for its text and background, automatically adapting to the active theme.

Conclusion

Following these steps, you can easily implement dynamic themes in your Next.js project.

You can find more articles, written by me on my website.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay