Implementing dark mode in React

Last update: 23 May, 2020
Table of contents

In this post, you’ll see 4 methods you can use to add dark mode in a React application. You will also see their limitations and some of React’s limitations. If I were to start a new project, I would use only the last 2 methods or, even better, a library like Theme UI. Nevertheless, in my 100% unbiased opinion, I think it’s an interesting read.

Ideal solution

The ideal solution for implementing “dark mode” in a React application satisfies the following requirements:

  • The app supports SSR. If the app runs only on the client-side, then all solutions work without a problem. Here I use a static/prerendered app with Gatsby.
  • The app remembers the selected mode, so you’ll want to save it in local storage.
  • The app uses React’s Context API to implement dark mode. You may already be using something like styled-components themes not only for colors, but for implementing a design system, or for creating component primitives with styled-system and rebass. As a result, you may want the styling to be in one place, and you don’t want to switch between styled-components, CSS, or inline styling.

Spoiler: None of the solutions checks all the boxes above. Let’s take them one by one to see why this happens. We’ll start with the context.

This is not a step by step guide and also covers some advanced React topics. Check this GitHub repository that has the full code. I will also give a link with the relevant branch in each section.

Dark mode with context (no local storage)

GitHub repo master branch

In the first solution, you’ll use context, but you won’t save the theme in the local storage. To implement the context, I’m using a blog post by Kent C. Dodds called How to use react context effectively. (See also How to optimize your context value). The truth is that I’m not applying the performance optimizations from the linked posts because they are not relevant in our case—more about that in a bit.

Create a new file theme-context.jsx and place it in the src/context folder. The file looks like this:

src/context/theme-context.jsx
import React, {
  useState,
  useContext,
  useCallback,
  createContext,
} from "react";
import { ThemeProvider as BaseThemeProvider } from "styled-components";

import { lightTheme, darkTheme } from "../themes";

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [themeString, setThemeString] = useState("light");
  const themeObject = themeString === "dark" ? darkTheme : lightTheme;
  return (
    <ThemeContext.Provider value={{ themeString, setThemeString }}>
      <BaseThemeProvider theme={themeObject}>
        {children}
      </BaseThemeProvider>
    </ThemeContext.Provider>
  );
};

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context)
    throw new Error("useTheme must be used within a ThemeProvider");
  const { themeString, setThemeString } = context;
  const toggleTheme = useCallback(() => {
    if (themeString === "light") setThemeString("dark");
    else if (themeString === "dark") setThemeString("light");
  }, [themeString]);
  return {
    theme: themeString,
    toggleTheme,
  };
}

export { ThemeProvider, useTheme };

Let’s try to break down what’s going on in this file. In short, we’re creating a new context for the theme that we can use inside our components with a hook. More specifically:

  • We create a new component that stores the current theme in the state. The current theme is a string that can be "light" or "dark". We call the component ThemeProvider but think of it as a regular React component that has some state and renders some children.
  • We use the theme from the state as the value for a new context (ThemeContext). We wrap the children of the previous component with the ThemeContext.Provider (See how to use context in React docs).
  • In the same component, we use the theme provider from styled-components (BaseThemeProvider). We wrap the children of the component with the BaseThemeProvider and synchronize the two themes when the first changes. We synchronize the two themes with the themeObject variable which is also the value for the styled-components context.
  • We’ll wrap Gatsby’s root element with the ThemeProvider component (in a bit) in gatsby-browser.js and gatsby-ssr.js files.
  • Finally, we create a hook that uses the new context. Inside that hook, we show an error if someone tries to use the context outside of a provider (see the previous step). We also memoize the toggleTheme function with a useCallback hook, but, in this case, it’s not that important because we won’t pass that function as a prop to a component. You can remove it if you want. To use the theme context in a component, you only have to use the hook.

You might be wondering why are we creating a new context for the current theme, instead of using the context from styled-components. We do that because we want inside the context value the function that toggles the theme, something that the context from styled-components doesn’t seem to offer.

Let’s now talk about the performance optimizations we skipped—you can safely skip this paragraph. The value we pass to the theme context is an object. We pass it with value={{ themeString, setThemeString }}. This means that we are creating and passing a different object in each render. The components that use the context render when the value of the context changes. In a hypothetical scenario where a parent of the ThemeProvider component renders, the ThemeProvider component should also render. Because the value of the context is different in every render, that means that the components that use the context should also render. These would be some unnecessary renders because the theme didn’t change. For this reason, many people memoize the context value with the useMemo hook to ensure that it doesn’t change when the theme is the same. Additionally, they wrap the components that use the theme with memo that prevents re-renders when the props are the same. See this codesandbox from Kent C. Dodds that illustrates that. In our case, we’re not wrapping the ThemeProvider component in a parent component that renders all the time. The only time the ThemeProvider renders is when the theme changes, and, in this case, we want the components that use the context to render. That’s why I skipped the performance optimizations.

Next, you want to create the styled-components theme objects in src/themes/ folder. I said that I want to use the theme for other things, not only for colors but let’s use them only for colors to keep the code simple:

src/themes/index.js
const lightTheme = {
  bg: "white",
  bgDark: "pink",
  color: "black",
  accent: "blue",
};

const darkTheme = {
  bg: "#2c1320",
  bgDark: "#15090f",
  color: "white",
  accent: "#ef86a9",
};

export { lightTheme, darkTheme };

Then, you wrap the Gatsby’s root element in gatsby-browser.js and gatsby-ssr.js. The files are identical; just copy-and-paste the same code:

gatsby-ssr.js/gatsby-browser.js
import React from "react";
import "normalize.css";
import "typeface-fira-sans";
import "typeface-merriweather";

import { ThemeProvider } from "./src/context/theme-context";
import GlobalStyle from "./src/components/GlobalStyle";

export const wrapRootElement = ({ element }) => (
  <ThemeProvider>
    <>
      <GlobalStyle />
      {element}
    </>
  </ThemeProvider>
);

You also want a Toggle component that will switch between light and dark mode. For this reason, I copied the Toggle component from Dan Abramov’s blog, which is a slightly altered Toggle from react-toggle. In the comments, it says that it has some accessibility improvements. So you can either copy the Toggle.js, Toggle.css, and add the sun/moon images from Dan Abramov’s blog, or just use the default toggle from react-toggle.

You’ll place the toggle somewhere in the Header component. I’m also using the theme colors, in the Container component, and the theme hook:

src/components/header.js
import React from "react";
import { Link } from "gatsby";
import styled from "styled-components";
import PropTypes from "prop-types";
import Toggle from "./Toggle";

import sun from "../images/sun.png";
import moon from "../images/moon.png";
import { useTheme } from "../context/theme-context"; 

const Container = styled.header`
  color: ${({ theme }) => theme.color};
  background-color: ${({ theme }) => theme.bgDark};
  margin-bottom: 1.45rem;

  a {
    text-decoration: none;
    color: ${({ theme }) => theme.color};
  }
`;

const Header = ({ siteTitle }) => {
  const { theme, toggleTheme } = useTheme(); 
  return (
    <Container>
      <div
        style={{
          margin: `0 auto`,
          maxWidth: 960,
          padding: `1.45rem 1.0875rem`,
        }}
      >
        <h1 style={{ margin: 0 }}>
          <Link to="/">{siteTitle}</Link>
        </h1>
        <Toggle
          defaultChecked={theme === "dark" ? true : false}
          onChange={toggleTheme}
          icons={{
            checked: (
              <img
                style={{ pointerEvents: "none" }}
                width="16"
                height="16"
                alt="moon"
                aria-hidden
                src={moon}
              />
            ),
            unchecked: (
              <img
                style={{ pointerEvents: "none" }}
                width="16"
                height="16"
                alt="sun"
                aria-hidden
                src={sun}
              />
            ),
          }}
        />
      </div>
    </Container>
  );
};

Header.propTypes = {
  siteTitle: PropTypes.string,
};

Header.defaultProps = {
  siteTitle: ``,
};

export default Header;

And this is what the app looks like after the changes:

Dark mode with context

It works fine in both development and production modes, but every time you reload the page, it defaults back to light mode. If your app doesn’t reload much, it might be ok; but you never know how the user will use your app. As a result, this behavior can become annoying really fast. To fix that, you can save the preferred user theme in local storage:

Dark mode with context and local storage

GitHub repo context-local-storage-bug branch

I will use a custom hook to save the theme string in local storage. Alternatively, you can use an NPM package for that. You can use the useLocalStorage hook by giving a name string (theme) and the initialValue (“light”). It returns the value and a function to change it. The hook keeps in the state the value, and when the value changes, it runs a side-effect to save the value in the local storage. It initializes the state with the existing value in local storage or with the initialValue from the user. It also has some guards for SSR (windowGlobal variable).

src/hooks/useLocalStorage.js
import { useState, useEffect } from "react";

export const useLocalStorage = (name, initialValue) => {
  const windowGlobal = typeof window !== "undefined" && window;
  const [value, setValue] = useState(() => {
    if (windowGlobal) {
      const currentValue = windowGlobal.localStorage.getItem(name);
      return currentValue ? JSON.parse(currentValue) : initialValue;
    }
    return initialValue;
  });

  useEffect(() => {
    if (windowGlobal)
      windowGlobal.localStorage.setItem(name, JSON.stringify(value));
  }, [name, value, windowGlobal]);
  return [value, setValue];
};

To use this hook in the ThemeProvider, you only have to change 2 lines of code:

src/context/theme-context.jsx
import React, { useContext, useCallback, createContext } from "react";
import { ThemeProvider as BaseThemeProvider } from "styled-components";

import { useLocalStorage } from "../hooks/useLocalStorage"; 
import { lightTheme, darkTheme } from "../themes";

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [themeString, setThemeString] = useLocalStorage(
    "theme",
    "light"
  );
  const themeObject = themeString === "dark" ? darkTheme : lightTheme;
  return (
    <ThemeContext.Provider value={{ themeString, setThemeString }}>
      <BaseThemeProvider theme={themeObject}>
        {children}
      </BaseThemeProvider>
    </ThemeContext.Provider>
  );
};

function useTheme() {
  // skipping hook implementation
}

export { ThemeProvider, useTheme };

If you try it in development, it works fine. You can switch the theme by pressing the toggle, and if you reload the page, the app remembers your preference. But if you build it with gatsby build, and serve it locally with gatsby serve, you’ll see the following:

The bug with context and local storage

If you choose the dark mode, the theme is getting stored correctly in local storage, but if you reload the page you see the light mode. Also, the Toggle component is now broken. It needs some extra clicks to change the theme after reloading the page (if you had dark mode selected). If you log in the console the themes and context, you’ll see that everything is fine; the dark mode is selected as expected. So, why is this happening?

This happens because of how React’s hydrate method treats differences between client and server. During build time, we apply the default light theme, so we add to the DOM elements the styled-components classes for the light theme. When we go to the client and try to hydrate the markup, React sees that we now have a different theme from local storage, so we want to apply different classes for the dark mode. React doesn’t do that for attributes. Only for text mainly because of timestamps differences.

You can see that if you create a post context with 3 posts, and store them the same way you store the theme in local storage. After you save them in local storage, you can comment one post out to create a difference between the server (3 posts) and the client (2 posts). You’ll see that React will hydrate the text correctly, as it’s shown in the following video:

GitHub repo posts branch

React hydrates the text correctly

In this case, you can force an extra render when the component mounts to update the classes. If you do that, this is what the app looks like in production:

Forcing an extra render

GitHub repo context-local-storage (correct)

It works now, but you momentarily see a flash of unstyled content. This happens because you go quickly from light to dark mode. It’s quite noticeable because the color differences are big. You can tone it down if you add a CSS transition in the items that change color, but you’ll have to do this for every item. Also, if your app takes some time to load, or the users access your app from a slow network, they will be stuck for a while with the incorrect theme until the app becomes interactive.

It seems that you can’t have a perfect solution if you only use context. In the following method, you’ll run some code before React to manipulate the DOM and change the colors with CSS variables:

Dan Abramov’s solution with CSS

In this section, I will only give a brief overview of this method. If you want the full code, please check the CSS branch on the GitHub repository

You saw earlier that you can’t rely on context due to the way React’s hydrate method treats differences between client and server. So you’ll have to do something else. You’ll want to run your JavaScript code that will initialize the dark mode in a script tag (before React loads). You’ll place that script right after the opening body tag because you want the body element to exist, but you don’t want the browser to render something on the screen and see that flash again. This works because inline scripts block rendering. In a Gatsby application, one of the ways you can add a script tag is by overriding the default html.js file.

Inside that script, you declare an IIFE where you do the following:

  • You get the saved theme from local storage and save the correct one if necessary.
  • You perform a matchMedia query for a preferred color scheme. If the user has a preferred color scheme, you aggregate the saved and the preferred, giving priority to the saved.
  • You add a class to the body element for the preferred mode (dark, light). You declare your colors with CSS custom properties under body.light and body.dark (meaning that the colors will change when the class of the body changes).
  • You attach to the window object the current theme and some methods that you’ll use inside your header component later to change the theme.
src/html.js
import React from "react";
import PropTypes from "prop-types";

export default class HTML extends React.Component {
  render() {
    return (
      <html {...this.props.htmlAttributes}>
        <head>
          <meta charSet="utf-8" />
          <meta httpEquiv="x-ua-compatible" content="ie=edge" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no"
          />
          {this.props.headComponents}
        </head>
        <body {...this.props.bodyAttributes} className="light">
          <script
            dangerouslySetInnerHTML={{
              __html: `
              (function() {
                window.__onThemeChange = function() {};
                function setTheme(newTheme) {
                  window.__theme = newTheme;
                  preferredTheme = newTheme;
                  document.body.className = newTheme;
                  window.__onThemeChange(newTheme);
                }
                var preferredTheme;
                try {
                  preferredTheme = localStorage.getItem('theme');
                } catch (err) { }
                window.__setPreferredTheme = function(newTheme) {
                  setTheme(newTheme);
                  try {
                    localStorage.setItem('theme', newTheme);
                  } catch (err) {}
                }
                var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
                darkQuery.addListener(function(e) {
                  window.__setPreferredTheme(e.matches ? 'dark' : 'light')
                });
                setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light'));
              })();
            `,
            }}
          />
          {this.props.preBodyComponents}
          <div
            key={`body`}
            id="___gatsby"
            dangerouslySetInnerHTML={{ __html: this.props.body }}
          />
          {this.props.postBodyComponents}
        </body>
      </html>
    );
  }
}

HTML.propTypes = {
  htmlAttributes: PropTypes.object,
  headComponents: PropTypes.array,
  bodyAttributes: PropTypes.object,
  preBodyComponents: PropTypes.array,
  body: PropTypes.string,
  postBodyComponents: PropTypes.array,
};

This is the code outside of the template string with comments that explain what it does:

(function () {
  // This is a callback that gets called by setTheme.
  // You’ll assign a setState function to this callback to
  // update the React state later inside the header component.
  window.__onThemeChange = function () {};
  // An inner function that changes the mode but
  // doesn’t save in local storage. You won’t use this.
  function setTheme(newTheme) {
    // This is used inside the header component
    // to set the initial state. It’s also used to keep track
    // of the mode when the header component mounts/unmounts.
    window.__theme = newTheme;
    preferredTheme = newTheme;
    document.body.className = newTheme;
    window.__onThemeChange(newTheme);
  }
  // Get the saved theme from local storage
  var preferredTheme;
  try {
    preferredTheme = localStorage.getItem("theme");
  } catch (err) {}
  // The function that changes the mode and saves
  // to local storage. You’ll use that inside the header
  // component.
  window.__setPreferredTheme = function (newTheme) {
    setTheme(newTheme);
    try {
      localStorage.setItem("theme", newTheme);
    } catch (err) {}
  };
  var darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
  // When the user changes the preferred color scheme,
  // call the window.__setPreferredTheme method.
  darkQuery.addListener(function (e) {
    window.__setPreferredTheme(e.matches ? "dark" : "light");
  });
  // Set the theme for the first time by aggregating the
  // saved theme (preferred theme variable) and the color
  // scheme query. Don't forget the parenthesis, they are
  // important
  setTheme(preferredTheme || (darkQuery.matches ? "dark" : "light"));
})();

Next, go to the Header component (because that’s where the Toggle component is) and do the following:

  • Get the correct theme from window.__theme in an effect that runs only on mount.
  • When the user clicks the Toggle to change the theme, you call the window.__setPreferredTheme() method. This method saves the theme in localStorage, and updates the body class and the React state.
  • The window.__onThemeChange() is a callback that gets called by window.setPreferredTheme() and updates the React state in this case.
src/components/header.jsx
import React, { useState, useEffect } from "react";
// skipped

const Container = styled.header`
  color: var(--color);
  background-color: var(--bgDark);
  margin-bottom: 1.45rem;

  a {
    text-decoration: none;
    color: var(--color);
  }
`;

const Header = ({ siteTitle }) => {
  const [theme, setTheme] = useState(null);
  useEffect(() => {
    setTheme(window.__theme);
    window.__onThemeChange = () => setTheme(window.__theme);
  }, []);
  return (
    <Container>
      <div
        style={{
          margin: `0 auto`,
          maxWidth: 960,
          padding: `1.45rem 1.0875rem`,
        }}
      >
        <h1 style={{ margin: 0 }}>
          <Link to="/">{siteTitle}</Link>
        </h1>
        {theme ? (
          <Toggle
            checked={theme === "dark"}
            onChange={(e) =>
              window.__setPreferredTheme(
                e.target.checked ? "dark" : "light"
              )
            }
            // skipping the rest
          />
        ) : (
          <div style={{ height: "28px" }} />
        )}
      </div>
    </Container>
  );
};

// skipped

Because you don’t want to render the Toggle with the wrong state on mount, you render a placeholder div element if the theme is null. The theme is null on mount both in development and production, which is a good thing because you don’t want differences between the development and production. An alternative is to use the useLayoutEffect instead of useEffect that will run the effect before React renders the component with the wrong state on screen. This is not a good practice, though, because it delays browser paints. So pretend I didn’t mention it and stick with the first option.

Now, you can use CSS variables for the colors instead of getting them from the styled-components theme. I define those variables in a GlobalStyle.jsx component in this example, but you can import a regular CSS file if you want:

src/components/GlobalStyle.jsx
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
    // skipped

    a {
        color: var(--accent);
    }

    body {
        background-color: var(--bg);
    }

    body.light {
      --bg: white;
    --bgDark: pink;
    --color: black;
    --accent: blue;
    }

    body.dark {
      -webkit-font-smoothing: antialiased;

    --bg: #2c1320;
    --bgDark: #15090f;
    --color: white;
    --accent: #ef86a9;
    }
`;
export default GlobalStyle;

If you build the application, this is how it looks. P.S. Not exactly, in the following video I render the Toggle in the wrong state on mount. You can see that when I change the theme to dark mode and refresh the page. Don’t worry though, the code above doesn’t do that.

The solution with CSS variables

By the way, in this example, I’m still wrapping the root element with a ThemeProvider from styled-components. I just don’t use the theme for the colors. See an out-of-the-box dark mode solution with Theme UI if you don’t like that. I should also note that CSS variables (or CSS custom properties) do not work on Internet Explorer.

A refactor of the CSS solution

GitHub repo css-refactor branch

When I first wrote this article more than a year ago, I treated Dan’s solution as a black box without explaining it much which is something I didn’t like. I made some edits in the previous section, but I also want to show you a refactor of this method.

This section needs a rewrite (a rewrite of the rewrite) because after writing it, I experimented a bit more and pushed to the repo more branches that use a context provider that I think are better. Check the GitHub repo (master branch) for those solutions.

The gist of this method is that you want to run the code that switches the mode in a script. In other words, you take the control from React and give it to an inline script. You do that because React doesn’t handle this use case well. In the next snippet, you can see the script code with syntax highlighting, outside of a string prop:

(function () {
  window.__onThemeChange = function () {};
  function setTheme(newTheme) {
    window.__theme = newTheme;
    preferredTheme = newTheme;
    document.body.className = newTheme;
    window.__onThemeChange(newTheme);
  }
  var preferredTheme;
  try {
    preferredTheme = localStorage.getItem("theme");
  } catch (err) {}
  window.__setPreferredTheme = function (newTheme) {
    setTheme(newTheme);
    try {
      localStorage.setItem("theme", newTheme);
    } catch (err) {}
  };
  var darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
  darkQuery.addListener(function (e) {
    window.__setPreferredTheme(e.matches ? "dark" : "light");
  });
  setTheme(preferredTheme || (darkQuery.matches ? "dark" : "light"));
})();

I find it a bit complicated, so, in the next snippet, you can find a more straight-forward (?) version with comments that explain what happens in each step:

// I use regular functions and var instead of
// const to support older browsers (not IE).
// I also give names to the functions for
// easier debugging.
(function initializeTheme() {
  // 1. Get the existing theme from local storage
  // and save it to window.__theme.
  try {
    window.__theme = localStorage.getItem("theme");
  } catch (err) {
    console.log("Couldn’t get the theme from local storage.");
  }

  // 2. Inside React, you “subscribe” to theme updates by
  // overwriting the following callback.
  window.__onThemeChangeCallback = function () {};

  // 3. The function that changes the theme and
  // calls the callback.
  window.__setTheme = function setTheme(newTheme) {
    // You’ll use that variable to set the initial
    // React state. It's also used to store the
    // correct mode when the component mounts
    // and unmounts.
    window.__theme = newTheme;
    // Change the class, call the callback, and
    // save the theme in local storage.
    document.body.className = newTheme;
    window.__onThemeChangeCallback(newTheme);
    try {
      localStorage.setItem("theme", newTheme);
    } catch (err) {
      console.log("Couldn’t save the theme in local storage.");
    }
  };

  // 4. A color scheme listener that calls the setTheme
  // method.
  var darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
  darkQuery.addListener(function darkQueryListener(e) {
    window.__setTheme(e.matches ? "dark" : "light");
  });

  // 5. Set the theme for the first time according to
  // the saved theme and the color scheme query.
  window.__setTheme(
    window.__theme || (darkQuery.matches ? "dark" : "light")
  );
})();

A problem with both of those snippets is that you can’t use the code inside the Toggle for more than one component. You can’t extract it to a useDarkMode hook for example because every time you assign a callback to onThemeChangeCallback, inside a React component, you overwrite the previous component’s callback. As a result, their state will get out of sync. If you want to use the theme only inside a Toggle component, you don’t have a problem. You can try to solve this by using the theme in a single context component, but you may face the same problems with hydration all over again. So instead of overwriting a single callback, you can keep track of a callback array, and then iterate through it inside the script and call them one-by-one. You can also overengineer the problem with an observer pattern.

(function initializeTheme() {
  try {
    window.__theme = localStorage.getItem("theme");
  } catch (err) {
    console.log("Couldn’t get the theme from local storage.");
  }

  var callbacks = [];
  window.__addCallback = function (cb) {
    callbacks.push(cb);
  };
  window.__removeCallback = function (cb) {
    callbacks = callbacks.filter(function (callback) {
      return callback !== cb;
    });
  };

  window.__setTheme = function setTheme(newTheme) {
    window.__theme = newTheme;
    document.body.className = newTheme;
    callbacks.forEach(function (cb) {
      cb();
    });
    try {
      localStorage.setItem("theme", newTheme);
    } catch (err) {
      console.log("Couldn’t save the theme in local storage.");
    }
  };

  var darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
  darkQuery.addListener(function darkQueryListener(e) {
    window.__setTheme(e.matches ? "dark" : "light");
  });

  window.__setTheme(
    window.__theme || (darkQuery.matches ? "dark" : "light")
  );
})();

This how to use the code above inside the Header component.

src/components/header.js
useEffect(() => {
  setTheme(window.__theme);
  const callback = () => setTheme(window.__theme);
  window.__addCallback(callback);
  return () => {
    window.__removeCallback(callback);
  };
}, []);
src/components/header.js
<Toggle
  checked={theme === "dark"}
  onChange={(e) =>
    window.__setTheme(e.target.checked ? "dark" : "light")
  }
/>

I use the last solution in this blog, and, as far as I know, it works. The only difference is that I don’t use a Layout component that unmounts on page change. Instead, I wrap Gatsby’s pageElement with the Layout.

Other things to read

Popular

Previous/Next