How to make an adaptive emoji card (TS React and Chakra UI)

arielbk - Oct 28 '23 - - Dev Community

I was experimenting with gradient borders and stumbled across an interesting technique โ€” cards that adapt to the content inside them. You can see a demo of the effect in action here.

Here's what we'll be building today:

Final demo

This card will magically adapt it's colours to the emoji passed to it, but a similar approach could be used for any kind of content.

Let's run through the steps from scratch and break it down!


Initialise the Project

We'll be building from the ground up without abstracting anything. The tech stack we'll be using is:

  • Vite with React and TypeScript: To quickly set up our project environment.
  • Chakra UI: It's not necessary for the effect, but Chakra's style props make styling easier.
  • Framer Motion: This is a peer dependency of Chakra UI and will come in handy if we decide to animate the card.

Set up the project with Vite: Start by running the following:

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Choose React with TypeScript when prompted.

Install Chakra UI and Framer Motion:

npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
Enter fullscreen mode Exit fullscreen mode

Create a Chakra theme: Save the following in theme.ts:

import { ThemeConfig, extendTheme } from '@chakra-ui/react';

const config: ThemeConfig = {
  initialColorMode: 'dark',
  useSystemColorMode: false,
};

const theme = extendTheme({
  config,
  styles: {
    global: () => ({
      body: {
        bg: '#121212',
      },
    }),
  },
});

export default theme;

Enter fullscreen mode Exit fullscreen mode

This sets the Chakra colour scheme to dark mode and applies a dark grey background.

Integrate the Chakra Provider: Modify src/main.tsx to include the Chakra provider:

import { ChakraProvider } from '@chakra-ui/react';
import theme from './theme';

...

<React.StrictMode>
    <ChakraProvider theme={theme}>
        <App />
    </ChakraProvider>
</React.StrictMode>
Enter fullscreen mode Exit fullscreen mode

Styling Adjustments: Ensure your page takes up the entire screen by setting min-height: 100vh; on the #root element within App.css.


Build the Basic Card

Let's lay the foundation by first creating a basic card component.

Create the Component: Create a src/components/EmojiCard.tsx component:

import { Box, BoxProps } from '@chakra-ui/react';

interface Props {
  emoji: string;
}

export default function EmojiCard({ emoji, children }: Props) {
  return (
    /* main container */
    <Box position="relative" maxW={700} borderRadius={8} bg="#181818">
      /* content wrapper */
      <Box
        px={8}
        py={4}
        gap={{ base: 0, sm: 8 }}
        display="flex"
        alignItems="center"
        flexDirection={{ base: 'column', sm: 'row' }}
        borderRadius={6}
      >
        /* emoji container */
        <Box
          display="flex"
          alignItems="center"
          justifyContent="center"
          fontSize="80px"
          width="200px"
        >
          {emoji}
        </Box>
        /* text content container */
        <Box height="100%" textAlign={{ base: 'center', sm: 'left' }}>
          {children}
        </Box>
      </Box>
    </Box>
  );
}

Enter fullscreen mode Exit fullscreen mode

Box is a component from Chakra UI that allows us to pass in style props directly.

In this basic setup we have a surrounding main container with a constrained width and a background. We also have a content wrapper on the inside with a place for our emoji and for the text content.

This code serves as a base structure for our card.

Render the Component: Update App.tsx to render our new component:

import EmojiCard from "./components/EmojiCard";

...

<EmojiCard emoji="๐Ÿฌ">
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi explicabo doloremque, accusantium repellat dolorem natus soluta quos!
</EmojiCard>
Enter fullscreen mode Exit fullscreen mode

The basic card

Now we have something to work with!


The Effect

With the initial setup done, let's get into the fun part โ€” creating the effect!

Background Emoji: Add the emoji as a background within the main container but before the content wrapper:

<Box
  position="absolute"
  left={0}
  top={0}
  width="100%"
  height="100%"
  justifyContent={'center'}
  alignItems={'center'}
  display="flex"
  _before={{
    content: `"${emoji}"`,
    fontSize: 80,
  }}
/>;
Enter fullscreen mode Exit fullscreen mode

Emoji over the content

This places a duplicate emoji in the middle of the card. Notice how it goes over the content even though the background component is before our content in the code.

Because the background element has an absolute position, and our content has a default static position, CSS's stacking context puts the background over the content. To fix this, set the content wrapper box to position="relative".

Emoji under the content

Styling Tweaks: Increase the emoji size by adjusting the font size up to 1100, and add a filter to the emoji background element: filter={"blur(80px)"}

Gradient background

This is an interesting gradient effect, but we want to confine it to our card. Add an overflow="hidden" to the main container.

Gradient contained in card

Now let's add the following to the content wrapper to cut out a space for content in the middle, and get a subtle border with these gradient colours:

backgroundColor="#151515"
border="2px solid transparent"
backgroundClip="padding-box"
Enter fullscreen mode Exit fullscreen mode

The interesting part here is the backgroundClip property. It determines how far the background extends within an element.

padding-box means the backgroundColor we set will extend to the outer edge of the padding, but won't go under the border.

Gradient border

This card should now respond to any emoji you pass into it and use it to fill the background gradient:

Gradient border card with different emoji


Add Polish

Let's enhance this effect further!

Gradient Background: Replace the backgroundColor property we've set with the following:

backgroundImage="linear-gradient(rgb(20 20 20 / 0.8), rgb(20 20 20))"
Enter fullscreen mode Exit fullscreen mode

This will replace the solid background with a light gradient, slightly transparent at the top, so that some of the colour from the background can shine through:

Emoji card with gradient background

Animate the Background: We can make the card a little more dynamic and eye-catching by animating the emoji background using Framer Motion:

import { motion, useTime, useTransform } from 'framer-motion';

const AnimatedBox = motion(Box);

...

// inside our component
const time = useTime();
const rotate = useTransform(
  time,
  [0, 16000], // every 16 seconds...
  [0, 360], // ...rotate 360deg
  { clamp: false }, // repeat the animation
);

...

// convert our emoji background to a framer motion component
<AnimatedBox
  // pass in our special framer motion value
  style={{ rotate }}
    ...
Enter fullscreen mode Exit fullscreen mode

We convert the Box component for the emoji background into an AnimatedBox using Framer Motion's motion utility. This allows the component to accept special values that Framer Motion animates for us outside of React's standard rendering cycle.

We use the useTime and useTransform hooks from Framer Motion to calculate how much the emoji should rotate. Then we pass the rotate value as a style prop to our Framer Motion component to handle the animation.

Animated demo

This is the final result in action.

Well done for making it this far! You can check out the final code for this on the CodeSandbox here, and the original experimentation demo shows it in action with some different emojis.


It's exciting to stumble across techniques like this. We've just scratched the surface, and there are lots of different directions you could take.

Imagine this effect with different light modes, or as an icon button that self-styles based on the provided icon.

You're not restricted to emojis; throw in any image, pattern, or coloured text. Experiment with different borders. Play around with the filter blur โ€” remove it, or use another.

Let us know in the comments how you went!

. . . . . . . .