Build a Reusable Responsive Card component with styled-components

Christopher T.
March 2nd, 2019

Hi, my name's christopher and this is my first blog post! I decided to open myself to the world as I'd been a long time lurker. But now that I've recently scored a full-time web developer job, I decided I wanted to help other developers and become involved more in the web development community! So if you like my blog posts and my attitude, feel free to subscribe to my newsletters, where i'll be regularly posting interesting blog posts about react, javascript, and the web development world in general!

Let's get moving.

Card components are often used because they keep layouts looking neat, clean and elegant. But they're also used because they keep related content closely grouped together to help represent its full content located elsewhere. The common components that make up a Card component are usually a title, date, description, and some additional information like a comments button that insist user action on a particular topic.

In this post i'll be guiding you to create a card component using the styled-components library--an amazing tool used to build styled react components that is capable of building very complex UI components.

To get started with this tutorial you are going to need to install styled-components (You can optionally install react-icons which will be used to display icons in the Card).

npm i  --save styled-components

We'll start by creating the Card component:

Card.js

import React from 'react'
import styled from 'styled-components'

const Card = (props) => <div />

export default Card

We mentioned that usually the components that make up a Card component are usually a title, date, and description, so we'll go ahead and apply this to the Card:

import React from 'react'
import styled from 'styled-components'

const Card = (props) => (
  <div style={{ color: '#fff' }}>
    <h2>The Benefits of Green Apples</h2>
    <div>3/2/2019</div>
    <div>
      Green apples have a high fiber content which helps in increasing the
      body's metabolism. While consuming an apple, make sure that you're not
      tossing the peel in the trash. Consuming apple with its peel improves the
      overall health. Due to its high fiber content, apple helps in
      detoxification process. It keeps the liver and digestive system away from
      harmful elements.
    </div>
  </div>
)

export default Card

Now that we have the basic structure down, we can proceed with implementing styled-components into the mix.

My recommendation for a flexible Card component is to create a react component for each component in the Card. That way, we can style each component separately and compose them with other components if needed. So lets start by creating styled components for the title, date, and description:

const Title = styled.h2`
  color: #fff;
  font-weight: 300;
`

const Date = styled.div`
  color: #ccc;
  font-weight: 300;
  margin: 6px 0;
`

const Description = styled.p`
  color: #fff;
  font-weight: 300;
`

Our Card component should now look a little nicer. We also benefited from self-documenting code:

import React from 'react'
import styled from 'styled-components'

const Card = (props) => (
  <div style={{ color: '#fff' }}>
    <Title>The Benefits of Green Apples</Title>
    <Date>3/2/2019</Date>
    <Description>
      Green apples have a high fiber content which helps in increasing the
      body's metabolism. While consuming an apple, make sure that you're not
      tossing the peel in the trash. Consuming apple with its peel improves the
      overall health. Due to its high fiber content, apple helps in
      detoxification process. It keeps the liver and digestive system away from
      harmful elements.
    </Description>
  </div>
)

export default Card

Not too shabby, right? But a Card component can optionally include additional buttons like viewing comments or pressing the like button. These user actions are most commonly found on social media sites like instagram or facebook, and indicate that the user can invoke an action about that topic.

For our buttons we'll implement comments, likes, and views:

const ActionButton = styled.button`
  margin: 0 5px;
  padding: 8px 14px;
  background: rgba(155, 155, 155, 0.2);
  color: #fff;
  cursor: pointer;
  border: 1px solid #fff;
  outline: 0;
  font-weight: 300;
  :hover {
    opacity: 0.8;
  }
`
import React from 'react'
import styled from 'styled-components'

const Card = (props) => (
  <div style={{ color: '#fff' }}>
    <Title>The Benefits of Green Apples</Title>
    <Date>3/2/2019</Date>
    <Description>
      Green apples have a high fiber content which helps in increasing the
      body's metabolism. While consuming an apple, make sure that you're not
      tossing the peel in the trash. Consuming apple with its peel improves the
      overall health. Due to its high fiber content, apple helps in
      detoxification process. It keeps the liver and digestive system away from
      harmful elements.
    </Description>
    <ActionButton>0 Comments</ActionButton>
    <ActionButton>0 Likes</ActionButton>
    <ActionButton>0 Views</ActionButton>
  </div>
)

export default Card

What we'll do next is to actually make this a re-usable component by transfering the ownership of applying content to the renderer (parent) component. The parent will now be the one calling the shots on what content to display by passing in props.title,props.date, props.description, and props.actions:

const Content = (props) => {
  const date = new Date().toLocaleDateString()
  const buttons = [
    {
      label: '0 Comments',
    },
    {
      label: '242 Likes',
    },
    {
      label: '187288 Views',
    },
  ]

  return (
    <Card2
      title="The Benefits of Green Apples"
      date={date}
      description="Green apples have a high fiber content which helps in increasing the
      body's metabolism. While consuming an apple, make sure that you're not
      tossing the peel in the trash. Consuming apple with its peel improves
      the overall health. Due to its high fiber content, apple helps in
      detoxification process. It keeps the liver and digestive system away
      from harmful elements."
      actions={buttons}
    />
  )
}

And in our Card component, we'll simply just grab these props and apply them inside the render method:

const Card2 = ({ title, date, description, actions }) => (
  <div style={{ color: '#fff' }}>
    <Title>{title}</Title>
    <Date>{date}</Date>
    <Description>{description}</Description>
    {actions.map(({ label }) => (
      <Action>{label}</Action>
    ))}
  </div>
)

A common method to make these buttons stand out more and help convey their purpose is to place icons in the buttons right next to the text. We can start adding some icons from the react-icons package so that this looks a little more friendly:

import { FaCommentAlt, FaThumbsUp, FaRegEye } from 'react-icons/fa'

And apply them to the buttons array that we created recently:

const buttons = [
  {
    label: (
      <>
        <FaCommentAlt /> 0 Comments
      </>
    ),
  },
  {
    label: (
      <>
        <FaThumbsUp /> 242 Likes
      </>
    ),
  },
  {
    label: (
      <>
        <FaRegEye /> 187288 Views
      </>
    ),
  },
]

Here's how the card component looks like now:

Not bad!

However, the icons look a little off in terms of positioning. If you look closely they seem to a little higher than the text next to them.

A strategy we'll use is to build a wrapper component that will wrap the action buttons.

The purpose of this wrapper component is to align all of the buttons with the same exact styles that its sibling buttons are using, by implementing a selector to target and style them all at once:

const Actions = styled.div`
  color: #333;
  display: flex;
  align-items: center;
  svg {
    transform: translateY(2px);
    margin-right: 5px;
  }
`

Now wrap the action buttons in the Card component (Note: I also spread ...props in the .map loop to each Action button so that we can forward additional props to the button element if provided by the parent):

const Card = ({
  title,
  date,
  description,
  comments,
  likes,
  views,
  actions,
}) => (
  <div style={{ color: '#fff' }}>
    <Title>{title}</Title>
    <Date>{date}</Date>
    <Description>{description}</Description>
    <Actions>
      {actions.map(({ label, ...props }) => (
        <Action {...props}>{label}</Action>
      ))}
    </Actions>
  </div>
)

Hurray, we're done!

For your convenience i'm going to provide the entire code with additions to some custom styling to make it "pop".

Here is the final code (includes media queries for responsive design):

Card.js:

import React from 'react'
import styled from 'styled-components'

const StyledContainer = styled.div`
  border: ${(props) => `1px solid ${props.theme.border.cool}`};
  padding: 25px 12px 18px;
  background: ${(props) => `linear-gradient(
    45deg, ${props.theme.primary.main}, ${props.theme.secondary.main}
  )`};
`

const Title = styled.h2`
  color: #fff;
  font-weight: 300;
  @media (max-width: 500px) {
    font-size: 1rem;
  }
`

const Date = styled.div`
  color: #ccc;
  font-weight: 300;
  margin: 6px 0;
  @media (max-width: 500px) {
    font-size: 0.8rem;
  }
`

const Description = styled.p`
  color: #fff;
  font-weight: 300;
  @media (max-width: 500px) {
    font-size: 0.75rem;
  }
`

const Actions = styled.div`
  color: #333;
  display: flex;
  align-items: center;
  svg {
    transform: translateY(2px);
    margin-right: 5px;
  }
  @media (max-width: 500px) {
    flex-direction: column;
    & button {
      width: 100%;
      margin-bottom: 4px;
      font-size: 0.65rem;
    }
  }
`

const Action = styled.button`
  margin: 0 5px;
  padding: 8px 14px;
  background: rgba(155, 155, 155, 0.2);
  color: #fff;
  cursor: pointer;
  border: 1px solid #fff;
  outline: 0;
  font-weight: 300;
  :hover {
    opacity: 0.8;
  }
  :active {
    background: ${(props) => props.theme.primary.main};
  }
`

const Card = ({
  title,
  date,
  description,
  comments,
  likes,
  views,
  actions,
}) => (
  <StyledContainer>
    <Title>{title}</Title>
    <Date>{date}</Date>
    <Description>{description}</Description>
    <Actions>
      {actions.map(({ label }) => (
        <Action>{label}</Action>
      ))}
    </Actions>
  </StyledContainer>
)

export default Card

Parent.js

import React from 'react'
import styled from 'styled-components'
import { FaCommentAlt, FaThumbsUp, FaRegEye } from 'react-icons/fa'
import Card from './Card'

const StyledRoot = styled.div`
  padding: 50px 12px;
`

const StyledContainer = styled.div`
  max-width: 550px;
  width: 100%;
  margin: auto;
`

const Parent = () => {
  const date = new Date().toLocaleDateString()

  const onCommentClick = () => alert('You clicked comments')
  const onLikesClick = () => alert('You clicked comments')
  const onViewsClick = () => alert('You clicked comments')

  const buttons = [
    {
      label: (
        <>
          <FaCommentAlt /> 0 Comments
        </>
      ),
      onClick: onCommentClick,
    },
    {
      label: (
        <>
          <FaThumbsUp /> 242 Likes
        </>
      ),
      onClick: onLikesClick,
    },
    {
      label: (
        <>
          <FaRegEye /> 187288 Views
        </>
      ),
      onClick: onViewsClick,
    },
  ]

  return (
    <StyledRoot>
      <StyledContainer>
        <Card
          title="The Benefits of Green Apples"
          date={date}
          description="Green apples have a high fiber content which helps in increasing the
      body's metabolism. While consuming an apple, make sure that you're not
      tossing the peel in the trash. Consuming apple with its peel improves
      the overall health. Due to its high fiber content, apple helps in
      detoxification process. It keeps the liver and digestive system away
      from harmful elements."
          actions={buttons}
        />
      </StyledContainer>
    </StyledRoot>
  )
}

export default Parent

Final Result:

Bonus

To spice up your new Card component even further, you can add an image element above the title to help visualize the Card's topic:

const StyledPhoto = styled.img`
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: ${(props) => `1px solid ${props.theme.border.cool}`};
`
const Card = ({
  title,
  date,
  description,
  comments,
  likes,
  views,
  actions,
}) => (
  <StyledContainer>
    <StyledPhoto src="https://s3-us-west-1.amazonaws.com/welcome_apples/posts/1_card/apple.jpg" />
    <Title>{title}</Title>
    <Date>{date}</Date>
    <Description>{description}</Description>
    <Actions>
      {actions.map(({ label }) => (
        <Action>{label}</Action>
      ))}
    </Actions>
  </StyledContainer>
)

Final Result:

Here's how it looks like for mobile devices:

Conclusion

As you can see now, card components keep layouts looking elegant and clean. Use card components whenever you want to keep related content closely grouped together, and have it link to the full content. A nicely designed card component can go a long way in getting clicks for content :)



© jsmanifest 2019