Using emoji as favicon dinamically with Next.js

Using emoji as favicon dinamically with Next.js

Introduction

On this post you'll learn how to use a emoji as favicon on your site. To achieve this, we'll build a simple web page that the user can change the favicon on the go. The web app will use:

  • Next.js as React framework;
  • Chakra UI as React component library

This blog post was inspired by this css-tricks post;

What you'll learn today

  • Clone a github boilerplate to bootstrap our project;
  • Use a emoji as a favicon;
  • Dynamically change the emoji with react state;

Prerequisites

What do you need to go through this post:

  • Basic knowledge of javascript;
  • Basic knowledge of React;
  • Node.js installed on your machine;
  • Git installed on your machine;

Cloning the boilerplate

The boilerplate project is hosted on Github. To clone the repo you'll run on your terminal:

git clone https://github.com/jpc0rrea/emoji-as-favicon-boilerplate.git <name-of-your-project>

Then, access the project folder and install the dependencies:

cd <name-of-your-project>

# I use yarn, but you can use npm also
yarn

# run the project
yarn dev

Now the application is running on the port 3000. If you can access localhost:3000 you'll see this:

boilerplate-project-print.png

As you can see, the favicon still is the Vercel logo. We'll change that now.

Using a emoji as the favicon

To use a emoji as the favicon is really simple. You only need to add one line of code. This is possible now that the browsers supports SVG favicons. The line of code is:

<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🦦</text></svg>">

In a Next.js project like ours, we need to put this on the _document.tsx file, on the <Head> component. So let's do this:

// _document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ColorModeScript } from '@chakra-ui/react';

import { theme } from '../styles/theme';

export default class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>

          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link
            rel="preconnect"
            href="https://fonts.gstatic.com"
            crossOrigin="true"
          />
          <link
            href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
            rel="stylesheet"
          />
          <link
            rel="icon"
            href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🦦</text></svg>"
          ></link>
        </Head>
        <body>
          <ColorModeScript initialColorMode={theme.config.initialColorMode} />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Now our favicon has changed! You can see our beautiful otter 🦦 as the favicon.

boilerplate-project-with-otter.png

Dynamically change our favicon with the user interaction

The _document.tsx file it's only loaded when the project is loaded, so we can't change nothing dynamically there. But, we can use the Next.js <Head> component on the homepage of our app to change the favicon as we wish. So, let's remove the <link> that we recently coded at the _document.tsx and let's change the index.tsx file:

// index.tsx
import { Heading } from '@chakra-ui/react';
import type { NextPage } from 'next';
import Head from 'next/head';

const Home: NextPage = () => {
  return (
    <>
      <Head>
        <title>Boilerplate project</title>
        <meta name="description" content="Generated by create next app" />
        <link
          rel="icon"
          href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🦦</text></svg>"
        ></link>
      </Head>

      <Heading>Boilerplate project</Heading>
    </>
  );
};

export default Home;

The result should be the same, the otter still is our favicon. Now, let's change this a little bit. To control which emoji it's going to be the favicon let's use the useState hook of React.

// index.tsx
import { Heading } from '@chakra-ui/react';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useState } from 'react';

const Home: NextPage = () => {
  const [favicon, setFavicon] = useState('🦦');

  return (
    <>
      <Head>
        <title>Boilerplate project</title>
        <meta name="description" content="Generated by create next app" />
        <link
          rel="icon"
          // 👇🏽 We have to change the href tag to use `` instead of '',
          // 👇🏽 so we can use javascript variables inside the string.
          href={`data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${favicon}</text></svg>`}
        ></link>
      </Head>

      <Heading>Boilerplate project</Heading>
    </>
  );
};

export default Home;

Nothing much, but we're now using a state as the favicon. Now let's create a component that will act as a button, so the user can choose the favicon he wants.

Creating the EmojiCard component

Inside the src folder, let's create a components folder. Inside the new folder, let's create a EmojiCard.tsx file. We will use Chakra UI components to speed up the creation:

// EmojiCard.tsx
import { Flex, Heading, useColorModeValue } from '@chakra-ui/react';

interface EmojiCardProps {
  // the emoji that this card will show
  emoji: string;
  // the onClick handler for this card
  onClick: () => void;
  // the actual favicon, so we can style the active card accordingly
  favicon: string;
}

export function EmojiCard({ emoji, onClick, favicon }: EmojiCardProps) {
  // useColorModeValue because we're going to implement dark mode soon
  const cardBackgroundColor = useColorModeValue(
    'brand.backgroundGray',
    'gray.50'
  );

  return (
    <>
      <Flex
        backgroundColor={cardBackgroundColor}
        margin={['4', '8']}
        width="80%"
        height="80%"
        as="button"
        borderRadius="10"
        justify="center"
        align="center"
        transition="all .4s ease"
        border="6px solid"
        // add a yellow border to the active card/ emoji
        borderColor={favicon === emoji ? 'brand.yellow' : 'transparent'}
        shadow={['md', 'lg']}
        _hover={{
          transform: 'scale(1.05)',
          cursor: 'pointer',
          transition: 'all .4s ease',
        }}
        onClick={onClick}
      >
        <Heading fontSize="7xl">{emoji}</Heading>
      </Flex>
    </>
  );
}

Great! Now, let's use this component on the homepage. First, let's create an array that will store the emoji options, so we can map over it to render all the cards. Also, let's add some borders and padding, so our page can breathe. The index.tsx will turn into this:

// index.tsx
import { Flex, Heading, SimpleGrid } from '@chakra-ui/react';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useState } from 'react';
import { EmojiCard } from '../components/EmojiCard';

const LIST_OF_EMOJIS = [
  '🐶',
  '🐱',
  '🐭',
  '🐹',
  '🐰',
  '🦊',
  '🐻',
  '🐼',
  '🐨',
  '🐯',
  '🦁',
  '🐮',
  '🐷',
  '🐸',
  '🐵',
  '🙈',
  '🙉',
  '🙊',
  '🐒',
  '🐔',
  '🐧',
  '🐦',
  '🐤',
  '🐣',
  '🐥',
  '🦆',
  '🦭',
  '🦦',
];

const Home: NextPage = () => {
  const [favicon, setFavicon] = useState('🦦');

  return (
    <>
      <Head>
        {/* 👇🏽 change the title and the description of the homepage */}
        <title>Emoji as Favicon</title>
        <meta
          name="description"
          content="A website that changes the favicon when the user select a new emoji"
        />
        <link
          rel="icon"
          // 👇🏽 We have to change the href tag to use `` instead of '',
          // 👇🏽 so we can use javascript variables inside the string.
          href={`data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${favicon}</text></svg>`}
        />
      </Head>

      <Flex direction="column" h="100vh">
        <Flex
          as="header"
          w="100%"
          h="20"
          mx="auto"
          mt="4"
          px="6"
          align="center"
          maxH="20"
        >
          <Flex w="100%" justifyContent="space-between">
            {/* Let's show the actual favicon on this Heading too! */}
            <Heading as="h2" size="2xl" fontWeight="bold" letterSpacing="tight">
              Emoji as favicon {favicon}
            </Heading>
          </Flex>
        </Flex>
        <SimpleGrid
          flex="1"
          gap="4"
          align="center"
          paddingBottom="6"
          justifyContent="space-between"
          // a css trick to always fill the screen with cards on the best way
          templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
        >
          {LIST_OF_EMOJIS.map((emoji) => {
            return (
              <EmojiCard
                key={emoji}
                emoji={emoji}
                favicon={favicon}
                // when the user clicks on the card, turn the emoji into the favicon
                onClick={() => setFavicon(emoji)}
              />
            );
          })}
        </SimpleGrid>
      </Flex>
    </>
  );
};

export default Home;

Perfect! Now we're changing the favicon when the users clicks on a card.

https://media4.giphy.com/media/yZlGf5XXEd3NxEhiXl/giphy.gif?cid=790b7611f9d8f50792b1ce088ce58997fe9412bc843f12da&rid=giphy.gif&ct=g

Add a randomize button (and light/ dark theme support)

Our boilerplate already has dark/ light theme supports! We just need to add a button so the users can changes the actual theme. For that, let's create a ToogleThemeButton.tsx file on the components folder. As I showed on this blog post, the components is something like this:

// ToogleThemeButton.tsx
import { IconButton, useColorMode, useColorModeValue } from '@chakra-ui/react';
import { RiMoonLine, RiSunLine } from 'react-icons/ri';

export function ToogleThemeButton() {
  const { colorMode, toggleColorMode } = useColorMode();

  const buttonBackgroundColor = useColorModeValue(
    'gray.50',
    'brand.backgroundGray'
  );

  const buttonBackgroundColorOnHover = useColorModeValue(
    'gray.300',
    'whiteAlpha.300'
  );

  return (
    <IconButton
      aria-label="Toogle theme"
      icon={colorMode === 'light' ? <RiMoonLine /> : <RiSunLine />}
      onClick={toggleColorMode}
      fontSize="20"
      backgroundColor={buttonBackgroundColor}
      _hover={{
        backgroundColor: buttonBackgroundColorOnHover,
      }}
    />
  );
}

Of course that we need to install the react-icons package:

yarn add react-icons

Now we can add the ToogleThemeButton on our header, like that:

// index.tsx
// ...
<Flex w="100%" justifyContent="space-between">
    {/* Let's show the actual favicon on this Heading too! */}
  <Heading as="h2" size="2xl" fontWeight="bold" letterSpacing="tight">
    Emoji as favicon {favicon}
   </Heading>

   {/* add the ToogleThemeButton to the header */}
   <Flex>
    <ToogleThemeButton />
   </Flex>
</Flex>
// ...

To create a randomize button it's pretty straightforward. We just need to create a function to randomize the favicon and add a button to execute the function! The function can be like that:

// index.tsx
// ...
const handleRandomizeClick = () => {
    const randomEmoji =
    LIST_OF_EMOJIS[Math.floor(Math.random() * LIST_OF_EMOJIS.length)];

    setFavicon(randomEmoji);
};
// ....

Now, let's add the button to the header:

// index.tsx
// ...
<Flex w="100%" justifyContent="space-between">
    {/* Let's show the actual favicon on this Heading too! */}
    <Heading as="h2" size="2xl" fontWeight="bold" letterSpacing="tight">
    Emoji as favicon {favicon}
    </Heading>

    {/* add the ToogleThemeButton and the randomize to the header */}
    <Flex>
        <Button
            onClick={handleRandomizeClick}
            colorScheme="yellow"
            marginRight="4"
        >
            Randomize!
        </Button>
        <ToogleThemeButton />
    </Flex>
</Flex>

And our index.tsx file will be this:

// index.tsx
import { Button, Flex, Heading, SimpleGrid } from '@chakra-ui/react';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useState } from 'react';
import { EmojiCard } from '../components/EmojiCard';
import { ToogleThemeButton } from '../components/ToogleThemeButton';

const LIST_OF_EMOJIS = [
  '🐶',
  '🐱',
  '🐭',
  '🐹',
  '🐰',
  '🦊',
  '🐻',
  '🐼',
  '🐨',
  '🐯',
  '🦁',
  '🐮',
  '🐷',
  '🐸',
  '🐵',
  '🙈',
  '🙉',
  '🙊',
  '🐒',
  '🐔',
  '🐧',
  '🐦',
  '🐤',
  '🐣',
  '🐥',
  '🦆',
  '🦭',
  '🦦',
];

const Home: NextPage = () => {
  const [favicon, setFavicon] = useState('🦦');

  const handleRandomizeClick = () => {
    const randomEmoji =
      LIST_OF_EMOJIS[Math.floor(Math.random() * LIST_OF_EMOJIS.length)];

    setFavicon(randomEmoji);
  };

  return (
    <>
      <Head>
        {/* 👇🏽 change the title and the description of the homepage */}
        <title>Emoji as Favicon</title>
        <meta
          name="description"
          content="A website that changes the favicon when the user select a new emoji"
        />
        <link
          rel="icon"
          // 👇🏽 We have to change the href tag to use `` instead of '',
          // 👇🏽 so we can use javascript variables inside the string.
          href={`data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${favicon}</text></svg>`}
        />
      </Head>

      <Flex direction="column" h="100vh">
        <Flex
          as="header"
          w="100%"
          h="20"
          mx="auto"
          mt="4"
          px="6"
          align="center"
          maxH="20"
        >
          <Flex w="100%" justifyContent="space-between">
            {/* Let's show the actual favicon on this Heading too! */}
            <Heading as="h2" size="2xl" fontWeight="bold" letterSpacing="tight">
              Emoji as favicon {favicon}
            </Heading>

            {/* add the ToogleThemeButton and the randomize to the header */}
            <Flex>
              <Button
                onClick={handleRandomizeClick}
                colorScheme="yellow"
                marginRight="4"
              >
                Randomize!
              </Button>
              <ToogleThemeButton />
            </Flex>
          </Flex>
        </Flex>
        <SimpleGrid
          flex="1"
          gap="4"
          align="center"
          paddingBottom="6"
          justifyContent="space-between"
          // a css trick to always fill the screen with cards on the best way
          templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
        >
          {LIST_OF_EMOJIS.map((emoji) => {
            return (
              <EmojiCard
                key={emoji}
                emoji={emoji}
                favicon={favicon}
                // when the user clicks on the card, turn the emoji into the favicon
                onClick={() => setFavicon(emoji)}
              />
            );
          })}
        </SimpleGrid>
      </Flex>
    </>
  );
};

export default Home;

That's the result:

https://media4.giphy.com/media/Tv5mbLBd0JOW18RsJi/giphy.gif?cid=790b76111b532548bc017aa8ef0b749f2c463de63e61b875&rid=giphy.gif&ct=g

Next step and challenges

Congratulations! You've made it! 🎉 🥳. Now, some challenges so you can improve your coding skills with Next.js and React:

  • Add all emojis separated by categories (like emojis-mart);
  • Add some animation when the emoji changes;
  • Add the source code of the <link> tag dynamically (so the user can copy and add to their website);

Source code of the post and live project

There are the link to the: