AdminKitDocs
Components

Next.js Bootstrap Cards

Next.js Bootstrap card markup for server components: header and title, images with asset(), cards mapped from data, next/link cards and CardActions.

The dashboards of AdminKit for Next.js are built from cards. Each card frames one chart, table or form under a title, so a busy page stays easy to scan. A card is Bootstrap's .card markup in JSX, with no wrapper component. Cards are part of the free and PRO versions.

That plain markup matters in the App Router, where pages are server components that render on the server. A card renders there and needs no "use client". Only what goes inside may be a client component, such as a chart or a dropdown.

Example

As on the demo pages, the title sits in the .card-header and the content in the .card-body:

<div className="card" style={{ maxWidth: "24rem" }}>
  <div className="card-header">
    <h2 className="card-title mb-0">Monthly report</h2>
  </div>
  <div className="card-body">
    <p className="card-text">
      Revenue grew in every region this month. The full report is ready to download.
    </p>
    <a href="#" className="btn btn-primary">Download</a>
  </div>
</div>

The style prop takes an object. On a page, size cards with the grid instead, such as <div className="col-12 col-lg-6">.

Classes

ClassEffect
cardThe container, with AdminKit's shadow and a bottom margin of one grid gutter
card-header, card-body, card-footerThe sections; a 1px border under the header
card-titleAdminKit: large, bold and muted at any heading level
card-subtitleA normal-weight line under the title; the demos write h6 card-subtitle text-body-secondary
card-text, card-linkBody text and links
card-img-top, card-img-bottomAn image on the top or bottom edge, as wide as the card
list-group list-group-flushAn edge-to-edge list inside the card

AdminKit styles cards in src/scss/3-components/_card.scss. It adds a shadow, a bottom margin and its own .card-title. Since .card-title sets its own size, the demo pages use <h2 className="card-title"> below the page <h1>. That keeps a correct heading order.

Header with a subtitle and actions

Pro

CardActions is only available in AdminKit PRO.

Add a menu to the header when a card has actions of its own. CardActions is the menu in the dashboard card headers. Put it before the title, since it floats right. Its menu items are placeholders: set your own in src/components/ui/card-actions.tsx. It wraps a Dropdown, which is a client component, but it takes no props, so a server component page renders it like any other component:

import { CardActions } from "@/components/ui/card-actions";

<div className="card">
  <div className="card-header">
    <CardActions />
    <h2 className="card-title">Browser usage</h2>
    <p className="h6 card-subtitle text-body-secondary">Last 30 days</p>
  </div>
  <div className="card-body">Chrome leads with 58% of the sessions.</div>
</div>

Cards from data in a page

A page that lists items, such as projects, can map its data straight to cards. This runs on the server at build time, so the cards are in the static HTML. For images, asset() from @/lib/site adds the base path to files in public/:

src/app/(dashboard)/projects/page.tsx
import { asset, pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/projects");

const projects = [
  { id: 1, name: "Website redesign", due: "June 12", image: 1 },
  { id: 2, name: "Mobile app", due: "July 3", image: 2 },
];

export default function Page() {
  return (
    <div className="row">
      {projects.map((project) => (
        <div key={project.id} className="col-12 col-md-6">
          <div className="card h-100">
            <img
              className="card-img-top"
              src={asset(`/img/photos/unsplash-${project.image}.jpg`)}
              alt={project.name}
              width="1200"
              height="800"
            />
            <div className="card-body">
              <h2 className="card-title">{project.name}</h2>
              <p className="card-text text-body-secondary">Due {project.due}</p>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

pageMetadata() reads the page's title and description from src/data/pages.json, so a new page needs an entry there (see Add a page). The demos use a plain <img> with width and height, which reserve the image's space before it loads. A static export has no image server, so next.config.ts sets images.unoptimized.

Clickable cards

When a card leads somewhere, let the user click anywhere on it. A link card, a.card, or a card with a .stretched-link lifts on hover and keyboard focus and shows the focus ring. With reduced motion, it stays in place. Use next/link for routes of the app:

import Link from "next/link";

<div className="card">
  <div className="card-body">
    <h2 className="card-title">
      <Link href="/pages-projects" className="stretched-link text-reset">
        Website redesign
      </Link>
    </h2>
    <p className="card-text">12 open tasks</p>
  </div>
</div>

Accessibility

  • Give each card a heading at the right level, so screen reader users can move from card to card.
  • On a clickable card, put the .stretched-link on the title: the link is then named after the title, not after every text in the card.
  • Describe each image in alt, or use alt="" for decoration.

Last updated on

On this page