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! ✌️

Top comments (31)

Collapse
 
shaglock profile image
Ilya Shaplyko • Edited

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

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

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
 
ldb profile image
LDB • Edited

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
 
franciscomendes10866 profile image
Francisco Mendes • Edited

Thanks 😊 Did you configure Tailwind?

Collapse
 
ldb profile image
LDB

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

Thread Thread
 
ldb profile image
LDB

screenshots:

imgur.com/a/gQ9EjBd

Thread Thread
 
franciscomendes10866 profile image
Francisco Mendes

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
 
johnatanetges profile image
Johnatan Etges

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

Collapse
 
abhishekpurecode profile image
abhishek

Designing modern cards with React and Tailwind CSS is a seamless process, marrying the efficiency of React components with the utility-first approach of Tailwind. Start by structuring a React component for the card, encapsulating content and functionalities. Integrate Tailwind's extensive set of utility classes to style the card, ensuring a sleek and responsive design. Leverage dynamic data binding in React to handle diverse card content effortlessly. Tailwind's flexibility allows for easy customization, enabling the creation of modern, visually appealing cards that align with the latest design trends while benefiting from React's modular and scalable architecture.
For more details, check out: purecode.ai/blogs/tailwind-cards/

Collapse
 
official_fire profile image
CoderZ90

Very beautiful UI design

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

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

Thanks 🤗

Collapse
 
optimbro profile image
Rahul

Great designs btw :)

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

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

Collapse
 
dolcekar profile image
Dolcekar

quick question, will Tailwind be conflicting with bootstrap for react library? I remember in 2018 I had an issue because I installed material UI and bootstrap to use for different elements. I am now picking up coding since that time and I feel like a beginner in many ways. The goal is to deploy this project to Heroku. Thanks!

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

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
 
ericamontes profile image
EricaMontes

Very Good Work and this is best information marry me spell

Collapse
 
pmioduszewski profile image
pmioduszewski

Nice one! THX!

Collapse
 
adwait12345 profile image
adwait12345

really great

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

Thank you very much 💪

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

Hello 👋

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