Building Cards, Buttons and Badges

Intermediate
12 min

Building Cards, Buttons and Badges

Buttons, cards and badges are the atoms of every interface, and getting them right once pays off on every page. In this lesson you will build a button system with variants and sizes, cards with media and equal heights, a clickable card, and status badges from utilities alone.

A button base with variants

Every button shares one skeleton — inline flex, padding, radius, weight, focus ring and disabled styling — and only the colors change per variant.

html
<!-- Primary --> <button class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">Save</button>

A secondary variant swaps in border border-gray-300 bg-white hover:bg-gray-50, a destructive one bg-red-600 hover:bg-red-700, and a ghost variant (text-gray-700 hover:bg-gray-100) suits toolbars. Sizes change only padding and text — px-3 py-1.5 text-sm, px-4 py-2 text-sm, px-5 py-2.5 text-base — while the radius stays identical so buttons look related.

Icon and loading states

Icon-only buttons need a square hit area and a text alternative:

html
<button class="inline-flex size-9 items-center justify-center rounded-md text-gray-600 hover:bg-gray-100" aria-label="Settings"> <svg class="size-5" aria-hidden="true">...</svg> </button>

size-9 gives a 36px target, aria-label supplies the announced name, and aria-hidden hides the icon from assistive technology. A loading button keeps its text and adds a size-4 animate-spin icon, disabled and aria-busy="true", so its width never changes.

Cards with media and equal heights

Three details make a good card: overflow-hidden so the image respects the radius, a flex column so the footer sits at the bottom, and h-full so cards in a row match heights:

html
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <article class="flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm"> <img class="aspect-video w-full object-cover" src="cover.jpg" alt="" /> <div class="flex flex-1 flex-col p-5"> <h3 class="text-lg font-semibold">Title</h3> <p class="mt-1 text-sm text-gray-600">Description.</p> <div class="mt-auto pt-4 text-sm text-gray-500">3 min read</div> </div> </article> </div>

Grid items stretch by default, h-full passes that height to the card, flex-1 lets the body grow, and mt-auto pushes the footer down, so every card in the row ends on the same line.

Clickable cards

When the whole card should be a link, keep one real <a> and stretch its hit area with a pseudo-element:

html
<article class="group relative rounded-xl border p-5 transition hover:border-blue-500 hover:shadow-md"> <h3 class="font-semibold group-hover:text-blue-600"> <a href="/post/1" class="after:absolute after:inset-0">Read the article</a> </h3> </article>

relative on the card and after:absolute after:inset-0 on the link make the whole card clickable while the link text stays the accessible name; other interactive elements inside need relative z-10.

Badges

Badges are small inline labels: rounded, tight padding, small bold text, and a tinted background with darker text from the same family:

html
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">Active</span> <span class="inline-flex items-center gap-1 rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800"> <span class="size-1.5 rounded-full bg-yellow-500"></span> Pending </span>

The 100/800 pairing keeps contrast readable; in dark mode use dark:bg-green-900/40 dark:text-green-300.

Common mistakes

  • Rounding the card but not clipping it, so the image shows square corners.
  • Making a card a <div onclick> instead of a stretched link; keyboard users cannot reach it.
  • Using opacity-50 for disabled buttons without the disabled attribute itself.
Quick Quiz
Question 1 of 2

Which utility pushes a card's footer to the bottom in a flex column?

Key Takeaways

  • Build buttons from one skeleton (inline-flex items-center gap-2 rounded-md px-4 py-2 font-semibold plus focus and disabled styles) and vary only colors.
  • Icon buttons need aria-label and a square size-*; loading buttons keep their width.
  • Cards use overflow-hidden rounded-xl border shadow-sm, a flex column with mt-auto, and h-full for equal heights.
  • Stretch a single link with after:absolute after:inset-0 to make a card clickable.
  • Badges pair a 100 background with an 800 text color from the same family.

Next lesson: Styling Forms and Inputs — build accessible text fields, selects, checkboxes and validation states.

Building Cards, Buttons and Badges - Tailwind CSS | CodeYourCraft | CodeYourCraft