Cara menggunakan react-native-render-html font size

All code from this tutorial is available in this repository, and a video version of this tutorial is available below.

Table of Contents

Providing users with a dark mode for your web application has become an expectation, and there are many ways to accomplish it. Typically this is most efficiently done by taking advantage of the power of CSS variables.

In this tutorial we are going to show how to bundle the entire dark mode feature into a single component that you can take with you and place inside any application.

This component will not only persist your choice of settings through a page close or refresh, it will also respect the user's


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
0 setting in their browser. Pretty cool!

So let's dive into it.

Live Demo

Before we start we'll begin by taking a look a demo of the final product, so you know what to expect from this tutorial. Once you have completed it, you will have your own component that you can drop into any application to achieve this functionality.

Prerequisites

I'll presume that you have a basic familiarity with React.

You do not need to be an expert. In fact we don't have a single stateful variable, or any hooks or lifecycle methods. The goal here (as it should always be) is to minimize complexity. We don't need them for this feature.

We will be using Create React App for this tutorial as it is an extremely easy way to quickly and easy establish a React application template that you can build on.

If you choose not to use CRA you should still be able to follow along with this tutorial. We will be writing pure CSS, but in order to copy the examples exactly you would need to have webpack setup with a CSS loader to support the


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
2 syntax for CSS files.

If you are not using


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
3 you can simply use a

/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
4 element for your CSS files in your

/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
5 rather than importing them.

We will also be using Typescript as is the default for every web project I built these days. If you are not super familiar with Typescript you should still be able to follow along, thee amount of explicit typing in these examples is minimal.

Lastly, I have included a section on adding tests for your component using React Testing Library. This section is optional.

Initialize the Project

If you are using CRA then run the following command (if you have your own existing project then disregard)

npx create-react-app dark-mode-example --template typescript

Enter fullscreen mode Exit fullscreen mode

Adding Styles

When the application loads it will determine the dark/light setting in the following order of priority:

  1. User's previous toggle setting
  2. User's browser preferences
  3. Light mode

We'll begin by creating the CSS that handles dark mode.


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
6


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

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

Enter fullscreen mode Exit fullscreen mode

  1. The

    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    7 selector matches the root element representing the DOM tree. Anything you place here will be available anywhere in the application. This is where will will create the CSS variables that hold the colours for our light theme.

  2. Here we set the colours for our

    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    8 theme. Using the attribute selector we target any element with a
    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    9 attribute on it. This is a custom attribute that we will be placing ourselves on the
    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    0 element.

  3. We set the background colour and text color of our application. This will always be the value of the

    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    1 and
    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    2 variables. The value of those variables will change depending on when the
    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    9 attribute is set due to the cascade. The dark values are set after the root values so if the selector applies the initial (light) value of those variables will be overwritten with the dark values.

Notice I have also added a custom link colour here which changes based on the value of the theme. You can add as many custom colours as you want here and have them all be controlled by your light/dark toggle. Try adding some more yourself!

Adding the Toggle Button

Next we will create a custom checkbox input to look like a toggle switch based on this example.

I won't comment on how this CSS works as it's not in the scope of this tutorial and not relevant to dark/light mode. The styles below are simply to override the look of the default HTML checkbox.

Add them below the above code in


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
6


/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}
6

/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

Enter fullscreen mode Exit fullscreen mode

Creating the DarkMode Component

Now we will create our

/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
6 component.

To start we are just going to focus on the structure of the component itself, no events or functions:

/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
7

import "./DarkMode.css";

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"
        />
        <div className="slider round">div>
      label>
      <span>🌒span>
    div>
  );
};

export default DarkMode;

Enter fullscreen mode Exit fullscreen mode

The

/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
8 element will be handling the state of our colour theme. When it is
/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
9 then dark mode is active, when it is not checked then light mode is active.

If you render this component you should have a nice looking custom toggle button without any functionality.

Cara menggunakan react-native-render-html font size

To make our toggle switch work, we have to attack some Javascript functions to the

import "./DarkMode.css";

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"
        />
        <div className="slider round">div>
      label>
      <span>🌒span>
    div>
  );
};

export default DarkMode;
0 event of the input that fires when the checkbox is toggled.

We also need to decide which mode we are going to show by default when the page or application is first loaded. There is a lot to unpack here; there will be explanations for what is happening with the numbered comments below the example.

/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
7

import "./DarkMode.css";
import { ChangeEventHandler } from "react";

// 1
const setDark = () => {

  // 2
  localStorage.setItem("theme", "dark");

  // 3
  document.documentElement.setAttribute("data-theme", "dark");
};

const setLight = () => {
  localStorage.setItem("theme", "light");
  document.documentElement.setAttribute("data-theme", "light");
};

// 4
const storedTheme = localStorage.getItem("theme");

const prefersDark =
  window.matchMedia &&
  window.matchMedia("(prefers-color-scheme: dark)").matches;

const defaultDark =
  storedTheme === "dark" || (storedTheme === null && prefersDark);

if (defaultDark) {
  setDark();
}

// 5
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
  if (e.target.checked) {
    setDark();
  } else {
    setLight();
  }
};

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"

          // 6
          onChange={toggleTheme}
          defaultChecked={defaultDark}
        />
        <div className="slider round">div>
      label>
      <span>🌒span>
    div>
  );
};

export default DarkMode;

Enter fullscreen mode Exit fullscreen mode

  1. We create functions called

    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    2 and
    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    3 which do exactly what the names describe. We want these to be as simple as possible. When we invoke them we expect the app to switch to either light or dark mode.

  2. This is how we handle persistance. Using localStorage will allow us to save a value and have it persist even after the user closes the app or reloads the page. Every time light or dark mode is set, we save that value in the

    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    4 property of
    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    5.

  3. This is where we set the

    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    9 (or light) value on the
    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    0 DOM element. This is what actually updates the colours in our app. When that attribute is added then the
    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    8 selector from our CSS becomes active and the dark colour variables are set (and vice versa).

  4. The section under comment 4 is where the "initial" state is established when the page is loaded before the actual toggle switch has been used.

    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    9 gets the value from
    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    5 if it exists.
    import "./DarkMode.css";
    import { ChangeEventHandler } from "react";
    
    // 1
    const setDark = () => {
    
      // 2
      localStorage.setItem("theme", "dark");
    
      // 3
      document.documentElement.setAttribute("data-theme", "dark");
    };
    
    const setLight = () => {
      localStorage.setItem("theme", "light");
      document.documentElement.setAttribute("data-theme", "light");
    };
    
    // 4
    const storedTheme = localStorage.getItem("theme");
    
    const prefersDark =
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches;
    
    const defaultDark =
      storedTheme === "dark" || (storedTheme === null && prefersDark);
    
    if (defaultDark) {
      setDark();
    }
    
    // 5
    const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
      if (e.target.checked) {
        setDark();
      } else {
        setLight();
      }
    };
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
    
              // 6
              onChange={toggleTheme}
              defaultChecked={defaultDark}
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    1 checks a media query for the user's browser settings for prefers-color-scheme. Lastly
    import "./DarkMode.css";
    import { ChangeEventHandler } from "react";
    
    // 1
    const setDark = () => {
    
      // 2
      localStorage.setItem("theme", "dark");
    
      // 3
      document.documentElement.setAttribute("data-theme", "dark");
    };
    
    const setLight = () => {
      localStorage.setItem("theme", "light");
      document.documentElement.setAttribute("data-theme", "light");
    };
    
    // 4
    const storedTheme = localStorage.getItem("theme");
    
    const prefersDark =
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches;
    
    const defaultDark =
      storedTheme === "dark" || (storedTheme === null && prefersDark);
    
    if (defaultDark) {
      setDark();
    }
    
    // 5
    const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
      if (e.target.checked) {
        setDark();
      } else {
        setLight();
      }
    };
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
    
              // 6
              onChange={toggleTheme}
              defaultChecked={defaultDark}
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    2 is meant to check both of those and decide whether to default to dark mode based on the 3 rules of priority we established at the beginning of this tutorial. If it evaluates to true, we set the app to dark mode before the component even renders. (Note the reason we can do this is we are targeting the
    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    0 attribute which will already exist.)

  5. This is the event handler function we have written to capture the change event that occurs when a user clicks the checkbox. If the box is

    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    9 we enable dark mode, otherwise light mode.

  6. We place the event handler we just created onto the

    import "./DarkMode.css";
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    0 attribute so it fires every time the checkbox changes. We also use the
    import "./DarkMode.css";
    import { ChangeEventHandler } from "react";
    
    // 1
    const setDark = () => {
    
      // 2
      localStorage.setItem("theme", "dark");
    
      // 3
      document.documentElement.setAttribute("data-theme", "dark");
    };
    
    const setLight = () => {
      localStorage.setItem("theme", "light");
      document.documentElement.setAttribute("data-theme", "light");
    };
    
    // 4
    const storedTheme = localStorage.getItem("theme");
    
    const prefersDark =
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches;
    
    const defaultDark =
      storedTheme === "dark" || (storedTheme === null && prefersDark);
    
    if (defaultDark) {
      setDark();
    }
    
    // 5
    const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
      if (e.target.checked) {
        setDark();
      } else {
        setLight();
      }
    };
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
    
              // 6
              onChange={toggleTheme}
              defaultChecked={defaultDark}
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    2 boolean value we established to determine if the checkbox is enabled by default.

Adding Tests (Optional)

Before we add this component to our app we can write a few tests to ensure it works as we expect it to.

Create React App comes prepackaged with React Testing Library. It will automatically pick up any

import "./DarkMode.css";
import { ChangeEventHandler } from "react";

// 1
const setDark = () => {

  // 2
  localStorage.setItem("theme", "dark");

  // 3
  document.documentElement.setAttribute("data-theme", "dark");
};

const setLight = () => {
  localStorage.setItem("theme", "light");
  document.documentElement.setAttribute("data-theme", "light");
};

// 4
const storedTheme = localStorage.getItem("theme");

const prefersDark =
  window.matchMedia &&
  window.matchMedia("(prefers-color-scheme: dark)").matches;

const defaultDark =
  storedTheme === "dark" || (storedTheme === null && prefersDark);

if (defaultDark) {
  setDark();
}

// 5
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
  if (e.target.checked) {
    setDark();
  } else {
    setLight();
  }
};

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"

          // 6
          onChange={toggleTheme}
          defaultChecked={defaultDark}
        />
        <div className="slider round">div>
      label>
      <span>🌒span>
    div>
  );
};

export default DarkMode;
7 files you create.

import "./DarkMode.css";
import { ChangeEventHandler } from "react";

// 1
const setDark = () => {

  // 2
  localStorage.setItem("theme", "dark");

  // 3
  document.documentElement.setAttribute("data-theme", "dark");
};

const setLight = () => {
  localStorage.setItem("theme", "light");
  document.documentElement.setAttribute("data-theme", "light");
};

// 4
const storedTheme = localStorage.getItem("theme");

const prefersDark =
  window.matchMedia &&
  window.matchMedia("(prefers-color-scheme: dark)").matches;

const defaultDark =
  storedTheme === "dark" || (storedTheme === null && prefersDark);

if (defaultDark) {
  setDark();
}

// 5
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
  if (e.target.checked) {
    setDark();
  } else {
    setLight();
  }
};

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"

          // 6
          onChange={toggleTheme}
          defaultChecked={defaultDark}
        />
        <div className="slider round">div>
      label>
      <span>🌒span>
    div>
  );
};

export default DarkMode;
8

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";

// 1
test("renders dark mode component", () => {
  render(<DarkMode />);

  // 2
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
  expect(inputElement).toBeInTheDocument();
});

// 3
test("toggles dark mode", () => {
  render(<DarkMode />);
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;

  // 4
  expect(inputElement.checked).toEqual(false);
  fireEvent.click(inputElement);
  expect(inputElement.checked).toEqual(true);

  // 5
  expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});

Enter fullscreen mode Exit fullscreen mode

  1. A simple test to ensure the component renders.

  2. The input has a role of

    import "./DarkMode.css";
    import { ChangeEventHandler } from "react";
    
    // 1
    const setDark = () => {
    
      // 2
      localStorage.setItem("theme", "dark");
    
      // 3
      document.documentElement.setAttribute("data-theme", "dark");
    };
    
    const setLight = () => {
      localStorage.setItem("theme", "light");
      document.documentElement.setAttribute("data-theme", "light");
    };
    
    // 4
    const storedTheme = localStorage.getItem("theme");
    
    const prefersDark =
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches;
    
    const defaultDark =
      storedTheme === "dark" || (storedTheme === null && prefersDark);
    
    if (defaultDark) {
      setDark();
    }
    
    // 5
    const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
      if (e.target.checked) {
        setDark();
      } else {
        setLight();
      }
    };
    
    const DarkMode = () => {
      return (
        <div className="toggle-theme-wrapper">
          <span>☀️span>
          <label className="toggle-theme" htmlFor="checkbox">
            <input
              type="checkbox"
              id="checkbox"
    
              // 6
              onChange={toggleTheme}
              defaultChecked={defaultDark}
            />
            <div className="slider round">div>
          label>
          <span>🌒span>
        div>
      );
    };
    
    export default DarkMode;
    
    9 so we would expect to be able to find the element by that role.

  3. A test to ensure that the component actually activates dark mode when the checkbox is toggled

  4. Use testing library's

    import React from "react";
    import { render, screen, fireEvent } from "@testing-library/react";
    import DarkMode from "./DarkMode";
    
    // 1
    test("renders dark mode component", () => {
      render(<DarkMode />);
    
      // 2
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
      expect(inputElement).toBeInTheDocument();
    });
    
    // 3
    test("toggles dark mode", () => {
      render(<DarkMode />);
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
    
      // 4
      expect(inputElement.checked).toEqual(false);
      fireEvent.click(inputElement);
      expect(inputElement.checked).toEqual(true);
    
      // 5
      expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
    });
    
    0 function we can simulate a click on our input. We assert before clicking that it should not be checked, then after clicking it should be checked.

  5. This component by design does have side effects and that's what this final assertion is aiming to detect. Although the component is only a small container for an input, it is designed to apply the

    import React from "react";
    import { render, screen, fireEvent } from "@testing-library/react";
    import DarkMode from "./DarkMode";
    
    // 1
    test("renders dark mode component", () => {
      render(<DarkMode />);
    
      // 2
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
      expect(inputElement).toBeInTheDocument();
    });
    
    // 3
    test("toggles dark mode", () => {
      render(<DarkMode />);
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
    
      // 4
      expect(inputElement.checked).toEqual(false);
      fireEvent.click(inputElement);
      expect(inputElement.checked).toEqual(true);
    
      // 5
      expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
    });
    
    1 attribute to the root
    /* Custom Dark Mode Toggle Element */
    .toggle-theme-wrapper {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 4px;
    }
    
    .toggle-theme-wrapper span {
      font-size: 28px;
    }
    
    .toggle-theme {
      position: relative;
      display: inline-block;
      height: 34px;
      width: 60px;
    }
    
    .toggle-theme input {
      display: none;
    }
    
    .slider {
      background-color: #ccc;
      position: absolute;
      cursor: pointer;
      bottom: 0;
      left: 0;
      right: 0;
      top: 0;
      transition: 0.2s;
    }
    
    .slider:before {
      background-color: #fff;
      bottom: 4px;
      content: "";
      height: 26px;
      left: 4px;
      position: absolute;
      transition: 0.4s;
      width: 26px;
    }
    
    input:checked + .slider:before {
      transform: translateX(26px);
    }
    
    input:checked + .slider {
      background-color: cornflowerblue;
    }
    
    .slider.round {
      border-radius: 34px;
    }
    
    .slider.round:before {
      border-radius: 50%;
    }
    
    0 element. That element can be accessed directly with the Javascript variable
    import React from "react";
    import { render, screen, fireEvent } from "@testing-library/react";
    import DarkMode from "./DarkMode";
    
    // 1
    test("renders dark mode component", () => {
      render(<DarkMode />);
    
      // 2
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
      expect(inputElement).toBeInTheDocument();
    });
    
    // 3
    test("toggles dark mode", () => {
      render(<DarkMode />);
      const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
    
      // 4
      expect(inputElement.checked).toEqual(false);
      fireEvent.click(inputElement);
      expect(inputElement.checked).toEqual(true);
    
      // 5
      expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
    });
    
    3. We check here that the
    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    8 value is applied to the attribute after the element is clicked.

If using the default CRA setup (or you have custom configured it to do so) we can run our tests with:

npm run test

Enter fullscreen mode Exit fullscreen mode

And get our results:

Cara menggunakan react-native-render-html font size

Adding DarkMode to the App

Below I have simply imported and added to the default App template created when you run Create React App.

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";

// 1
test("renders dark mode component", () => {
  render(<DarkMode />);

  // 2
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
  expect(inputElement).toBeInTheDocument();
});

// 3
test("toggles dark mode", () => {
  render(<DarkMode />);
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;

  // 4
  expect(inputElement.checked).toEqual(false);
  fireEvent.click(inputElement);
  expect(inputElement.checked).toEqual(true);

  // 5
  expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});
6

import React from "react";
import logo from "./logo.svg";
import "./App.css";
import DarkMode from "./DarkMode";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <DarkMode />
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsxcode> and save to reload.
        p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        a>
      header>
    div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Lastly, we need to update the default CSS included with the CRA setup, otherwise some of the color/background-color values will overwrite our theme variables.

The below example is the default version of

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";

// 1
test("renders dark mode component", () => {
  render(<DarkMode />);

  // 2
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
  expect(inputElement).toBeInTheDocument();
});

// 3
test("toggles dark mode", () => {
  render(<DarkMode />);
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;

  // 4
  expect(inputElement.checked).toEqual(false);
  fireEvent.click(inputElement);
  expect(inputElement.checked).toEqual(true);

  // 5
  expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});
7 with the color values commented out. You can delete them entirely if you like.

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";

// 1
test("renders dark mode component", () => {
  render(<DarkMode />);

  // 2
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
  expect(inputElement).toBeInTheDocument();
});

// 3
test("toggles dark mode", () => {
  render(<DarkMode />);
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;

  // 4
  expect(inputElement.checked).toEqual(false);
  fireEvent.click(inputElement);
  expect(inputElement.checked).toEqual(true);

  // 5
  expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});
8

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  /* background-color: #282c34; */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  /* color: white; */
}

.App-link {
  /* color: #61dafb; */
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

Enter fullscreen mode Exit fullscreen mode

If you followed along with everything you'll be treated to a very function app with your own custom component.

Cara menggunakan react-native-render-html font size

Setting Preferred Colour Scheme

We mentioned that this app supports the user's browser configuration for preferred colour scheme, but we didn't actually explain how you can set that value.

Unfortunately browsers do not make it easy, but it can be achieved with the following steps in either Chrome or Firefox:

Firefox

  • Type
    npm run test
    
    0 into your navigation bar
  • If it doesn't already exist create a value called
    npm run test
    
    1 and set it as a
    npm run test
    
    2
  • Se the number as 1 for
    
    /* 1 */
    :root {
      --font-color: #333;
      --background-color: #eee;
      --link-color: cornflowerblue;
    }
    
    /* 2 */
    [data-theme="dark"] {
      --font-color: #eee;
      --background-color: #333;
      --link-color: lightblue;
    }
    
    /* 3 */
    body {
      background-color: var(--background-color);
      color: var(--font-color);
    }
    
    a {
      color: var(--link-color);
    }
    
    8 or 0 for
    npm run test
    
    4

Chrome

  • Open developer tools (F12)
  • Click the ellipsis
    npm run test
    
    5 icon at the upper right of the tools
  • Click More Tools -> Rendering
  • Under "Emulate CSS Media" select "prefers-color-scheme: dark"

Chrome is a bit trickier so here is a screenshot showing where to find it:

Cara menggunakan react-native-render-html font size

Wrapping Up

I hope you enjoyed this tutorial and learned something from it! You might have picked up on the fact that although this was written from a React perspective, nearly all the code we are working with would work just as well independently of React.

Try it yourself the next time you are working with vanilla HTML/CSS, or even a different framework! You'll find that this code can be reused anywhere with very few modifications needed.

Please check some of my other learning tutorials. Feel free to leave a comment or question and share with others if you find any of them helpful: