DEV Community

Cover image for How to Create Modern Cards using React and Tailwind
Francisco Mendes
Francisco Mendes

Posted on

How to Create Modern Cards using React and Tailwind

Overview

One of my favorite components is undoubtedly cards and the reasons are very simple, they can have many uses.

In the same way that it can be used for informative content (such as brief descriptions), it can also be used to focus the user's attention momentarily (such as a post on social media) or so that the user can take an action.

When designing a card we need to pay attention to some aspects:

  • All content must be contained in a single element;
  • You don't need to have other auxiliary elements to give some context to the card's content;

From these small points we only need to pay attention to a few aspects in terms of the element's anatomy.

Obviously, many of the elements end up being optional and for that same reason today I'm only going to focus on just these:

  • Container - contains all the elements of the card and generally these same elements dictate the size of the card however, I like to do the opposite which is to define the maximum space they can occupy;
  • Thumbnail - it's an important visual element, however this article will be for stylistic reasons;
  • Buttons - usually the buttons are grouped in a single space and each button corresponds to an action that the user can take;

And one of the aspects that I find interesting is that it's a component that generally has great behaviors in terms of responsiveness.

Anyway, these are just a few aspects that I take into account when working with interface elements, so I recommend reading this Material Design page which explains in detail what kind of behavior this component should have.

Today's example

In today's example we will make a simple card, but in my opinion it has a very different design when compared to many libraries and frameworks. A behavior I recommend adopting is to spend a few minutes daily to see designs that you find interesting and I tried to copy them. I hope that at the end of today's article you have a similar result:

card final result

Let's code

The framework we are going to use today is Tailwind CSS and along with this framework we are going to use other tools such as classnames and react-icons.

npm install classnames react-icons
Enter fullscreen mode Exit fullscreen mode

After that we will create a file that contains the contents of the cards.

// @src/data/posts.js

export default [
  {
    title: "Rogue's Rise",
    likes: Math.floor(Math.random() * (50 - 0) + 0),
    image: "https://bit.ly/3BQdTqk",
  },
  {
    title: "Fool's End",
    likes: Math.floor(Math.random() * (50 - 0) + 0),
    image: "https://bit.ly/3CQFPvv",
  },
  {
    title: "A Greater Power",
    likes: Math.floor(Math.random() * (50 - 0) + 0),
    image: "https://bit.ly/3ERuyMd",
  },
  {
    title: "2099: Oasis",
    likes: Math.floor(Math.random() * (50 - 0) + 0),
    image: "https://bit.ly/3CQKSwb",
  },
];
Enter fullscreen mode Exit fullscreen mode

Now we can start working on our card but first let's create the styles for our component:

/* @src/components/Card.module.css */

.wrapper {
  @apply bg-white hover:bg-gray-800 shadow-xl hover:shadow-none cursor-pointer w-80 rounded-3xl flex flex-col items-center justify-center;
}

.wrapperAnime {
  @apply transition-all duration-500 ease-in-out;
}

.header {
  @apply relative mt-2 mx-2;
}

.imageWrapper {
  @apply h-56 rounded-2xl overflow-hidden;
}

.image {
  @apply object-cover w-full h-full;
}

.textWrapper {
  @apply pt-10 pb-6 w-full px-4;
}

.text {
  @apply font-medium leading-none text-base tracking-wider text-gray-400;
}

.badgeWrapper {
  @apply absolute bottom-0 left-0 -mb-4 ml-3 flex flex-row;
}

.dangerBadge {
  @apply h-10 w-10 flex items-center justify-center text-xl bg-white hover:bg-red-500 text-red-500 hover:text-white rounded-2xl shadow-xl;
}

.primaryBadge {
  @apply h-10 w-16 ml-2 bg-white hover:bg-blue-600 flex items-center justify-center font-medium text-blue-600 hover:text-white rounded-2xl shadow-xl;
}

.counter {
  @apply text-gray-800 ml-2;
}

.badgeAnime {
  @apply transform-gpu translate-y-0 hover:-translate-y-1 transition-all duration-300 ease-in-out;
}
Enter fullscreen mode Exit fullscreen mode

Now we can start working on our component's jsx. Our component will receive four props, which will be the title, the number of likes, the order of the array element and the image.

After that we can import our icons and we can start applying the styles of our component:

// @src/components/Card.jsx

import React from "react";
import classNames from "classnames";
import { AiFillHeart } from "react-icons/ai";
import { BsChatSquareFill } from "react-icons/bs";

import styles from "./Card.module.css";

const Card = ({ title, likes, order, image }) => {
  return (
    <div className={classNames([styles.wrapper, styles.wrapperAnime])}>
      <div className={styles.header}>
        <div className={styles.imageWrapper}>
          <img src={image} className={styles.image} alt="" />
        </div>
        <div className={styles.badgeWrapper}>
          <div
            className={classNames([styles.dangerBadge, styles.badgeAnime])}
          >
            <AiFillHeart />
          </div>
          <div
            className={classNames([
              styles.primaryBadge,
              styles.badgeAnime,
              "group",
            ])}
          >
            <BsChatSquareFill />
            <span
              className={classNames([styles.counter, "group-hover:text-white"])}
            >
              {likes}
            </span>
          </div>
        </div>
      </div>
      <div className={styles.textWrapper}>
        <h1 className={styles.text}>{`${order}. ${title}`}</h1>
      </div>
    </div>
  );
};

export default Card;
Enter fullscreen mode Exit fullscreen mode

Last but not least we have to go to our entry file (which in this case is App.jsx) and we will have the following styles:

/* @src/App.module.css */

.section {
  @apply bg-gray-100 h-full md:h-screen w-full;
}

.container {
  @apply container mx-auto px-0 md:px-4 py-4;
}

.layout {
  @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 justify-items-center gap-4;
}
Enter fullscreen mode Exit fullscreen mode

Now in our App.jsx we're going to import our data from the posts and our Card component that we created, then we're going to loop and pass in the necessary props.

// @src/App.jsx

import React from "react";

import styles from "./App.module.css";
import Card from "./components/Card";
import posts from "./data/posts";

const App = () => {
  return (
    <main className={styles.section}>
      <section className={styles.container}>
        <div className={styles.layout}>
          {posts.map((element, index) => (
            <Card
              key={index}
              title={element.title}
              likes={element.likes}
              order={index + 1}
              image={element.image}
            />
          ))}
        </div>
      </section>
    </main>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! ✌️

Discussion (31)

Collapse
shaglock profile image
Ilya Shaplyko • Edited on

Is there any reason why you moved the styles to separate CSS file with @apply instead of writing them directly in JSX?

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

It was simply because this way it's easier to read the code so I can do the coditional rendering properly and so I can make changes faster. It's just the approach I like to take when I work on styling components, in my view it's simpler and more intuitive to separate the contexts. 😊

Collapse
optimbro profile image
Rahul

I like this approach as well. So, when we use JIT mode, it still purges unused styles, and we have the same output as we were directly using utility classes in JSX right?

Thread Thread
j471n profile image
Jatin Sharma

The answer is No, By default, Tailwind only removes unused classes that it generates itself, or has been explicitly wrapped in a @layer directive. It will not remove unused styles from third-party CSS.

Thread Thread
franciscomendes10866 profile image
Francisco Mendes Author

It's correct 💪

Collapse
pra3t0r5 profile image
Pra3t0r5

I highly recommend not placing any styles inside JSX, instead, write a styles constant in the same file, and outside the components return function.

That way react performs way better, since inline styles are reprocessed with every state change.

Outside-return and imported styles do not suffer from this performance impact.

Collapse
shaglock profile image
Ilya Shaplyko

sorry, what I meant is not inline styles, but Tailwind classes.

Collapse
femil profile image
Femil Savaliya

because you can use one @apply style in multiple time in project :)

Collapse
lyndebayona profile image
Lynde Bayona • Edited on

Thanks for the nice article. But it didn't worked on me. Cards are so big seems styles wasn't applied even though I followed the instructions.

Collapse
johnatanetges profile image
Johnatan Etges

Hey! I got it! just follow the official starting guide. tailwindcss.com/docs/guides/create...

Collapse
johnatanetges profile image
Johnatan Etges

Same for me. Did you find what is going wrong?

Collapse
franciscomendes10866 profile image
Francisco Mendes Author • Edited on

Thanks 😊 Did you configure Tailwind?

Collapse
lyndebayona profile image
Lynde Bayona

I just installed tailwind using:
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

Thread Thread
lyndebayona profile image
Lynde Bayona

screenshots:

imgur.com/a/gQ9EjBd

Thread Thread
franciscomendes10866 profile image
Francisco Mendes Author

From the image you shared, I'm not sure tailwind is properly configured. I say this because the styles are not being applied. If you try to edit something, does it have any effect?

Collapse
official_fire profile image
CoderZ90

Very beautiful UI design

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

I'm glad you liked it 😊

Collapse
official_fire profile image
CoderZ90

Yep

Collapse
jishan profile image
G Shan

Nice

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks 🤗

Collapse
adwait12345 profile image
adwait12345

really great

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thank you very much 💪

Collapse
ericamontes profile image
EricaMontes

Very Good Work and this is best information marry me spell

Collapse
serwer20 profile image
pmioduszewski

Nice one! THX!

Collapse
rjitsu profile image
Rishav Jadon

Is there a way we could write the styles in html and generate the css file seperately and clean the html from class names?

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

If I understand your question correctly, I think my answer would be to use css-in-js, if you want to use it with Tailwind there is a library called twin.

Collapse
optimbro profile image
Rahul

Great designs btw :)

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks! But I actually put together two or three designs that I saw on dribbble to get this result. 😄

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Hello 👋

Some comments have been hidden by the post's author - find out more