DEV Community

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

Posted on

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.

Top comments (0)