Dark Mode with plain SASS vs Styled-Components
Having a dark theme on your app, it's not an option anymore. It's mandatory.
When we say dark mode, some people automatically think of hackers. But it's no longer a particularity for people who know how to hack the system and customise things on their computer. It has been a feature that allows even your gran to switch from light to dark theme on her smartphone easily.
Apple and Android added this feature to their systems in September of 2019 (from iOS 13 and Android 10). And more and more companies are adhering to the cause and making the dark mode default, automatically, at night.
This post will be more technical. And as a big fan of dark mode, I decided to show how easy it is to manage it if you plan it correctly. And I will do better. I will show you how to do it in two different ways while developing in React or React-Native, using plain SASS and using Styled-components.
But before putting the hands on the code, I consider it essential to show you some data and arguments to help you argue with anyone that it's not only a fancy option. We are in 2021, and your app must have a dark theme as well as it must be responsive.
The importance of dark mode
Snapchat increased the volume of searches by dark mode on Google by 156% due to the release of their dark mode feature on the app.
Android Authority, a trustful independent publication, did a poll with their users, tech enthusiasts. And the result is shocking.
Ok, Rafael, but most of the data you brought are related to tech enthusiasts, and it does not apply to all types of users. I agree with you partially. Please, have a look at this next statistic.
Polar App, a tool to manage documents annotations, and simplify learning from digital sources, discovered that 89% of their users prefer the dark mode.
During the 2018 Android Dev Summit, Google presented the battery usage of Google Maps drops by 63% on Pixel devices with AMOLED when the app runs in night mode.
Dark Theme using SASS
I developed a simple app to show you how I would do it. In this example, I'm using React + Context + SASS to set the theme class on the highest hierarchy container of your app.
Managing the theme switcher
- Create a theme controller component.
const ThemeController = ({ children }) => { const [theme] = useContext(ThemeContext); return ( <div className={`app app--${theme.mode}`}> { children } </div> ); }
export default ThemeController;
- Wrap your app with the theme controller.
const App = () => { return ( <StateProvider> <ThemeController> <Routes /> </ThemeController> </StateProvider> ); }
export default App;
- Create a switcher to allow the user to choose the theme.
import Switch from 'react-switch'; const Switcher = () => { const [theme, setTheme] = useContext(ThemeContext); const toggleMode = () => { setTheme({mode: theme.mode === 'light' ? 'dark' : 'light'}); }; return ( <div className="switcher"> Dark mode: <Switch className="switcher__toggler" onChange?={() => toggleMode()} checked={theme.mode === 'dark'} /> </div> ); }
export default Switcher;
- Create a context component to manage the current theme.
const initialTheme = { // set the default theme based on the current theme of user browser mode: (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light' }; export const ThemeContext = createContext(initialTheme); export const StateProvider = ({ children }) => { const [theme, setTheme] = useState(initialTheme); return ( <ThemeContext.Provider value={[theme, setTheme] }> { children} </ThemeContext.Provider> );
};
- On switch, set the state and use this state to add the correct class on your theme wrapper.
P.S.: Alternatively, you can do dark="true" or dark="false" instead of a className="app app--dark".
Managing the Styles
You have two ways to do it:
- Create a mixing for a dark mode that will allow you to add in each class @include dark-mode { ... } and customise each class individually.
- Create a src/styles/theme/dark-mode and manage all your variations there.
For bigger projects and thinking about what will be easier to manage in the future, I prefer to use the second option.
.app { width: 100%; height: 100vh; margin: 0; &.app--light { background-color: $white; color: $darkergrey; .header { background-color: $lightergrey; } .button__secondary--green { border-color: $darkgreen; color: $darkgreen; } .form { .form__label { &.required { color: $darkred; } } .form__input { background-color: transparent; &.required { border-color: $darkred; } } } } &.app--dark { background-color: $black; color: $lightgrey; .header { background-color: $darkergrey; } .form { .form__label { &.required { color: $lightred; } } .form__input { background-color: $darkergrey; &.required { border-color: $lightred; } } } } }
Dark Theme using Styled-Components
I developed a simple app to show you how I would do it. In this example, I'm using React Native + Context + Styled-components to set the theme mode on the highest hierarchy container of your app.
Before starting, install these libraries
- styled-components
- styled-theming
If you are using typescript you can install in your devDependencies these libraries
- @types/styled-components
- @types/styled-components-react-native
- @types/styled-theming
Managing the theme switcher
- Put the theme wrapper component inside of the Context Provider.
const App = () => { return ( <> <StateProvider> <ThemeProvider theme={{mode: 'light'}}> <StatusBar barStyle="dark-content" /> <StyledSafeAreaView> <LanguagesController> <AwsAuthenticator> <Routes /> </AwsAuthenticator> </LanguagesController> </StyledSafeAreaView> </ThemeProvider> </StateProvider> </> ); };
export default App;
- Create a switcher to allow the user to choose the theme. (Same as on SASS)
- On switch, set the state and use this state to set the mode on your theme wrapper. (Similar to SASS but instead of changing class, change the theme={{mode: theme}})
Managing the Style
This is the main difference between SASS and Styled-components. On styled-components instead of class with CSS properties, you will create components for each element of your app and style it using CSS.
You can find all the information you need in this documentation.
- Create a variable for the background, text or other using the theme('mode', { light: ..., dark: … }) that you will import from styled-theming.
- Set the background/text colour using the variable you set in the previous step.
import styled from 'styled-components/native'; import theme from 'styled-theming'; import {colors} from '@app/styles/theme/colors'; import {txt} from '@app/styles/theme/typography'; export const backgroundColor = theme('mode', { light: colors.white, dark: colors.grey.nine, }); export const StyledSafeAreaView = styled.SafeAreaView` flex: 1; background-color: ${backgroundColor}; `; export const AppView = styled.ScrollView` flex: 1; padding: 0 15px; background-color: ${backgroundColor};
`;
So… Which one is better?.
It depends on your project and your team.
For mobile apps using React Native, I certainly will choose the styled-components because you can style using CSS.
For web apps using React, I still prefer the SASS option simply because it's pure CSS, and I don't need to create a component for each element. But on the other hand, if you use styled-components you have more control, and it is easier to follow some patterns as you can use typescript with it.
However, in some cases, your team will need to learn how to use styled-component, and it requires some time. It's not so difficult to learn neither has a long learning curve, but if your team is majority made by junior developers, maybe they don't even have a significant experience with CSS yet, and you will put another obstacle for them, and it could delay your project.
It's up to you, please leave your comment here and tell me which one do you prefer and why.
See you in the next article.
Front-end Developer @ Luizalabs | React | Next.js | TypeScript | JavaScript | Storybook | Zustand
3 年Muito bom!