How to make an adaptive emoji card (TS React and Chakra UI)
Tags: tutorial, webdev, react, css
Published: 2023-10-28 15:22:28

---
title: How to make an adaptive emoji card (TS React and Chakra UI)
published: true
description: Learn how to make a modern card that adapts its colors seamlessly to any emoji input
tags: tutorial,webdev,react,css
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j1tshmm657fykqggsvrq.gif
# Use a ratio of 100:42 for best results.
# published_at: 2023-10-22 13:18 +0000
---

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](https://tuckshop-ui.vercel.app/).

Here's what we'll be building today:

![Final demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byyakjpisnpnjkz6oswb.png)

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:

```bash
npm create vite@latest
```

Choose React with TypeScript when prompted.

**Install Chakra UI and Framer Motion**:

```bash
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

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

```tsx
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;

```

   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:

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

...

<React.StrictMode>
	<ChakraProvider theme={theme}>
		<App />
	</ChakraProvider>
</React.StrictMode>
 ```

**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:

```tsx
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>
  );
}

```

	
`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:

```tsx
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>
```

![The basic card](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehqhsm7jrspg08finfq4.png)

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:

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

![Emoji over the content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yo69pzfbk9se2r1jnfp.png)

   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_](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context) puts the background over the content. To fix this, set the content wrapper box to `position="relative"`.

![Emoji under the content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zm1e2ww6560wzu5fos4l.png)

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bl9523rskn17xqhlsn9.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/trw35wzykamlw2oahjpf.png)

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:

```tsx
backgroundColor="#151515"
border="2px solid transparent"
backgroundClip="padding-box"
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j0up960rntbrt32c533f.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60klprhes2plaac1i6va.png)

---
## **Add Polish**

Let's enhance this effect further!

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

```tsx
backgroundImage="linear-gradient(rgb(20 20 20 / 0.8), rgb(20 20 20))"
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byyakjpisnpnjkz6oswb.png)


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

```tsx
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 }}
	...
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztlk6a9ektlljo5hc63q.gif)

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](https://codesandbox.io/p/github/arielbk/emoji-card-demo/main), and the [original experimentation demo](https://tuckshop-ui.vercel.app/) 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!