Skip to content

react18-tools/nextjs-themes-ultra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Nextjs-Themes-Ultra

test Maintainability codecov Version Downloads npm bundle size Gitpod ready-to-code

Nextjs-Themes-Ultra is a comprehensive library designed to unlock the full potential of React 18 server components. 🀟 πŸ‘‰ Unleash the Power of React Server Components

A canonical package with a longer and more descriptive name is also published as nextjs-themes-ultra

Motivation

I created the nextjs-themes library to achieve functionality similar to next-themes with React Server Components. While it worked well, I encountered issues with tree-shaking and noticed some rarely used functions could be removed or replaced to improve performance and readability.

I had plans to update the main library nextjs-themes, but it required ensuring minimal changes to the existing APIs. Therefore, I created a new library that has the potential to be a better alternative in most cases.

Features

βœ… Perfect dark mode with just 2 lines of code

βœ… Works with Tailwind CSS

βœ… Fully tree-shakable (import from nthul/client/theme-switcher)

βœ… Designed for excellence

βœ… Full TypeScript support

βœ… Unleash the full power of React 18 Server components

βœ… System setting with prefers-color-scheme

βœ… Themed browser UI with color-scheme

βœ… Support for Next.js 13 & 14 appDir

βœ… No flash on load (supports SSG, SSR, ISG, and Server Components)

βœ… Sync theme across tabs and windows (can opt-out by passing dontSync to ThemeSwitcher)

βœ… Apply custom transitions when changing themes

βœ… Force pages to specific themes (requires assigning className, detailed techniques coming soon)

βœ… Manipulate theme via the useTheme hook

βœ… Documented with Typedoc (Docs)

βœ… Use combinations of th- and dark or light classes for dark/light variants of themes

βœ… Avoids storing cookies when not using the corresponding ServerTarget

βœ… Compatible with all build systems/tools/frameworks for React 18

Feel free to request or discuss new features, or report bugs.

Please consider starring this repository and sharing it with your friends.

Install

A canonical package with a longer and more descriptive name is also published as nextjs-themes-ultra

$ pnpm add nthul

or

$ npm install nthul

or

$ yarn add nthul

Want a Lite Version? npm bundle size Version Downloads

$ pnpm add nthul-lite

or

$ npm install nthul-lite

or

$ yarn add nthul-lite

You need r18gs as a peer-dependency

Usage

Please explore examples and packages/shared-ui for working examples. (updates coming soon...)

SPA (e.g., Vite, CRA) and Next.js pages directory (No server components)

To add dark mode support, modify _app as follows:

import { ThemeSwitcher, ColorSwitch } from "nthul/client"; // for better tree-shaking

function MyApp({ Component, pageProps }) {
  return (
    <>
      <ThemeSwitcher />
      <header>
        <ColorSwitch />
      </header>
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

βš‘πŸŽ‰Boom! Just a couple of lines and your dark mode is ready, complete with a color switch for user preferences. Check out examples for advanced usage.

For vite or any other build tool, find a similar root component, e.g., <App /> in CRA and vite.

With Next.js app router (Server Components)

If your app serves mostly static content, you can avoid SSR overhead. When using this approach, use CSS general sibling combinator (~) to ensure your themed CSS is applied properly. See (HTML & CSS)[#html--css].

Update your app/layout.jsx to add ThemeSwitcher and ServerTarget. ServerTarget avoids a flash of un-themed content on reload.

// app/layout.jsx
import { ThemeSwitcher } from "nthul/client/theme-switcher"; // for better tree-shaking
import { ServerTarget } from "nthul/server/nextjs";

export default function Layout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        /** use ServerTarget as first element inside body */
        <ServerTarget />
        <ThemeSwitcher />
        {children}
      </body>
    </html>
  );
}

You just added multiple theme and color-scheme modes and can use Server Component! Isn't that awesome?

ColorSwitch

An elegant color switch to toggle color schemes ("dark" | "light" | "system").

<ColorSwitch />

HTML & CSS

Your Next.js app fully supports dark mode, including system preference with prefers-color-scheme. The theme is also synced between tabs. By default, nthul modifies the className on the html element, which you can use to style your app:

:root {
  --background: white;
  --foreground: black;
}

.dark {
  --background: black;
  --foreground: white;
}

/* for custom themes */

.th-theme1 {
  --background: red;
  --foreground: yellow;
}

/* for custom theme with dark and light variants */

.dark.th-theme2 {
  --background: blue;
  --foreground: white;
}

.light.th-theme2 {
  --background: white;
  --foreground: blue;
}

/* for scoped containers add .nth-scoped call as well - required only when using containerized themes. */
.nth-scoped.th-.dark {
  ...
}

.nth-scoped.th-theme.dark {
  ...
}

See the Example CSS file.

When using ServerTarget

When using ServerTarget, use the CSS general sibling combinator (~) since ServerTarget does not wrap your app.

Replace .selector with a combination of selectors from the description above.

.selector,
.selector *,
.selector ~ *,
.selector ~ * * {
  --th-variable: value;
}

We encourage using this pattern to define your theme variables in CSS to avoid unwanted overrides.

Images

You can also show different images based on the current theme.

import Image from "next/image";
import { useTheme } from "nthul/hooks";

function ThemedImage() {
  const { resolvedColorScheme } = useTheme();
  let src;

  switch (resolvedColorScheme) {
    case "light":
      src = "/light-mode-image.png";
      break;
    case "dark":
      src = "/dark-mode-image.png";
      break;
    default:
      src = "/default-image.png";
      break;
  }

  return <Image src={src} alt="Themed Image" />;
}

useTheme

In case your components need to know the current theme and be able to change it. The useTheme hook provides theme information:

import { useTheme } from "nthul";

const ThemeChanger = () => {
  const { theme, setTheme } = useTheme();

  return (
    <div>
      The current theme is: {theme}
      <button onClick={() => setTheme("light")}>Light Mode</button>
      <button onClick={() => setTheme("dark")}>Dark Mode</button>
    </div>
  );
};

useTheme hook will return following object.

interface UseTheme {
  theme: string;
  colorSchemePreference: "dark" | "light" | "system";
  systemColorScheme: "dark" | "light";
  resolvedColorScheme: "dark" | "light";
  setColorSchemePreference: (colorSchemePreference: ColorSchemePreference) => void;
  setTheme: (theme: string) => void;
}

Force per page theme and color-scheme

We have not added any components or hooks for forcing theme and color-scheme per page or per element basis. As this is a rarely used scenario. However, you can acheive this by applying appropreate calssNames.

// force a theme for the page
export default function Page() {
  return <div className="dark nth-scoped th-theme1">...</div>;
}

We are open to listening your feedback - Discussions

With Styled Components and any CSS-in-JS

Next Themes is completely CSS independent, it will work with any library. For example, with Styled Components you just need to createGlobalStyle in your custom App:

// pages/_app.js
import { createGlobalStyle } from "styled-components";
import { ThemeSwitcher } from "nthul";

// Your themeing variables
const GlobalStyle = createGlobalStyle`
  :root {
    --fg: #000;
    --bg: #fff;
  }

  [data-theme="dark"] {
    --fg: #fff;
    --bg: #000;
  }
`;

function MyApp({ Component, pageProps }) {
  return (
    <>
      <GlobalStyle />
      <ThemeSwitcher />
      <Component {...pageProps} />
    </>
  );
}

With Tailwind

In your tailwind.config.js, set the dark mode property to class:

// tailwind.config.js
module.exports = {
  darkMode: "class",
};

βš‘πŸŽ‰Boom! You are ready to use darkTheme in tailwind.

Caution! Your class must be set to "dark", which is the default value we have used for this library. Tailwind, as of now, requires that class name must be "dark" for dark-theme.

That's it! Now you can use dark-mode specific classes:

<h1 className="text-black dark:text-white">

Performance

nthul is designed to be fully tree-shakable and only includes the code you use. For example, if you only use the useTheme hook, the rest of the library's code will be removed during the build process.

Contributing

We welcome contributions! Please check out the Contributing Guide for more details.

🀩 Don't forget to star this repo!

Looking for a hands-on course to get started with Turborepo? Check out React and Next.js with TypeScript and The Game of Chess with Next.js, React, and TypeScript.

Repo Stats

License

MPL-2.0

Feel free to use, modify, and distribute this library as per the terms of the MPL-2.0 license.

Please consider enrolling in our courses or sponsoring our work.


with πŸ’– by Mayank Kumar Chaudhari