# 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](/nextjs/) 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`:

```html
<div class="card" style="max-width: 24rem;">
  <div class="card-header">
    <h2 class="card-title mb-0">Monthly report</h2>
  </div>
  <div class="card-body">
    <p class="card-text">Revenue grew in every region this month. The full report is ready to download.</p>
    <a href="#" class="btn btn-primary">Download</a>
  </div>
</div>
```
```tsx code
<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 class="col-12 col-lg-6">`.

## Classes

| Class | Effect |
| --- | --- |
| `card` | The container, with AdminKit's shadow and a bottom margin of one grid gutter |
| `card-header`, `card-body`, `card-footer` | The sections; a 1px border under the header |
| `card-title` | AdminKit: large, bold and muted at any heading level |
| `card-subtitle` | A normal-weight line under the title; the demos write `h6 card-subtitle text-body-secondary` |
| `card-text`, `card-link` | Body text and links |
| `card-img-top`, `card-img-bottom` | An image on the top or bottom edge, as wide as the card |
| `list-group list-group-flush` | An 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 class="card-title">` below the page `<h1>`. That keeps a correct heading order.

## Header with a subtitle and actions

> `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:

```html
<div class="card" style="max-width: 24rem;">
  <div class="card-header">
    <div class="card-actions float-end">
      <div class="dropdown position-relative">
        <a href="#" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Card actions"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis align-middle" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></a>
        <div class="dropdown-menu dropdown-menu-end">
          <a class="dropdown-item" href="#">Action</a>
          <a class="dropdown-item" href="#">Another action</a>
          <a class="dropdown-item" href="#">Something else here</a>
        </div>
      </div>
    </div>
    <h2 class="card-title">Browser usage</h2>
    <p class="h6 card-subtitle text-body-secondary">Last 30 days</p>
  </div>
  <div class="card-body">Chrome leads with 58% of the sessions.</div>
</div>
```
```tsx code
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/`:

```tsx title="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](/nextjs/docs/getting-started/project-structure/#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:

```tsx
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.

## Related

- [React Bootstrap cards](/react/docs/components/card/): the same markup in AdminKit for React (Vite)
- [Bootstrap 5 cards](/bootstrap/docs/components/card/): the cards of the HTML version
- [Tables](/nextjs/docs/components/tables/): a table that fills a card edge to edge
- [Cards demo](https://nextjs-demo.adminkit.io/ui-cards)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/components/card/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
