# React Bootstrap Tables

React Bootstrap table markup in AdminKit: rows rendered from an array with map and key, striped, hover and bordered tables, row colors and tables in cards.

Use a table for rows of data that people scan and compare: clients, orders, invoices. In [AdminKit for React](/react/), a table is Bootstrap's `.table` markup in JSX. You keep the rows in an array and render one `<tr>` per item. There is no table component to import. Tables are part of the free and PRO versions.

When people need to sort, search or page through the rows, use the [Data table](/react/docs/plugins/data-table/) plugin of AdminKit PRO instead.

## Example

```html
<table class="table">
  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Phone number</th>
      <th scope="col">Date of birth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Vanessa Tucker</td>
      <td>864-348-0485</td>
      <td>June 21, 1961</td>
    </tr>
    <tr>
      <td>William Harris</td>
      <td>914-939-2458</td>
      <td>May 15, 1948</td>
    </tr>
    <tr>
      <td>Sharon Lessman</td>
      <td>704-993-5435</td>
      <td>September 14, 1965</td>
    </tr>
  </tbody>
</table>
```
```tsx code
const people = [
  { id: 1, name: "Vanessa Tucker", phone: "864-348-0485", birthday: "June 21, 1961" },
  { id: 2, name: "William Harris", phone: "914-939-2458", birthday: "May 15, 1948" },
  { id: 3, name: "Sharon Lessman", phone: "704-993-5435", birthday: "September 14, 1965" },
];

export function PeopleTable() {
  return (
    <table className="table">
      <thead>
        <tr>
          <th scope="col">Name</th>
          <th scope="col">Phone number</th>
          <th scope="col">Date of birth</th>
        </tr>
      </thead>
      <tbody>
        {people.map((person) => (
          <tr key={person.id}>
            <td>{person.name}</td>
            <td>{person.phone}</td>
            <td>{person.birthday}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

Give each row a `key` from your data, such as an `id`. Always write `<thead>` and `<tbody>` yourself. React warns when a `<tr>` is a direct child of `<table>`.

## Classes

| Class | Effect |
| --- | --- |
| `table` | The base class |
| `table-striped` | Zebra stripes on the body rows |
| `table-hover` | A hover color on the body rows |
| `table-bordered`, `table-borderless` | Borders on all sides, or none |
| `table-sm` | Half the cell padding |
| `table-<color>` | A row or cell in a theme color: `table-primary`, `table-success`, ... |
| `table-responsive` | On a wrapper: scrolls the table sideways when it is too wide |
| `table-action` | AdminKit: muted icon links, 18px icons |

AdminKit centers the body cells vertically, and it fits tables into cards. A `.table` that is a direct child of a `.card`, or of a `.table-responsive` in a card, loses its outer borders and bottom margin. Its first and last cells get the card's padding, so the table fills the card from edge to edge. These card rules and the `.table-action` column style are in `src/scss/3-components/_tables.scss`.

## In a card, with actions

In a dashboard, a table usually sits in a card, often with a column of edit and delete links. The tables demo puts each table right after the `.card-header`, with the links in a `.table-action` cell:

```html
<div class="card">
  <div class="card-header">
    <h2 class="card-title mb-0">Clients</h2>
  </div>
  <table class="table table-striped table-hover">
    <thead>
      <tr>
        <th scope="col">Name</th>
        <th scope="col">Phone number</th>
        <th scope="col">Actions</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Vanessa Tucker</td>
        <td>864-348-0485</td>
        <td class="table-action">
          <a href="#" aria-label="Edit Vanessa Tucker"><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-pen align-middle" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg></a>
          <a href="#" aria-label="Delete Vanessa Tucker"><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-trash align-middle" aria-hidden="true"><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></a>
        </td>
      </tr>
      <tr class="table-success">
        <td>William Harris</td>
        <td>914-939-2458</td>
        <td class="table-action">
          <a href="#" aria-label="Edit William Harris"><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-pen align-middle" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg></a>
          <a href="#" aria-label="Delete William Harris"><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-trash align-middle" aria-hidden="true"><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></a>
        </td>
      </tr>
    </tbody>
  </table>
</div>
```
```tsx code
import { Pen, Trash } from "lucide-react";

<div className="card">
  <div className="card-header">
    <h2 className="card-title mb-0">Clients</h2>
  </div>
  <table className="table table-striped table-hover">
    <thead>
      <tr>
        <th scope="col">Name</th>
        <th scope="col">Phone number</th>
        <th scope="col">Actions</th>
      </tr>
    </thead>
    <tbody>
      {clients.map((client) => (
        <tr key={client.id} className={client.vip ? "table-success" : undefined}>
          <td>{client.name}</td>
          <td>{client.phone}</td>
          <td className="table-action">
            <a href="#" aria-label={`Edit ${client.name}`}>
              <Pen className="align-middle" />
            </a>{" "}
            <a href="#" aria-label={`Delete ${client.name}`}>
              <Trash className="align-middle" />
            </a>
          </td>
        </tr>
      ))}
    </tbody>
  </table>
</div>
```

The row color comes from the data. `className={client.vip ? "table-success" : undefined}` colors the VIP rows and leaves the attribute out for the others.

## Empty and loading states

An empty table body looks broken, so tell the user there is nothing to show. Render one row that spans every column:

```tsx
<tbody>
  {orders.length === 0 ? (
    <tr>
      <td colSpan={3} className="text-center text-body-secondary">
        No orders yet
      </td>
    </tr>
  ) : (
    orders.map((order) => (
      <tr key={order.id}>
        <td>{order.number}</td>
        <td>{order.customer}</td>
        <td className="text-end">{order.total}</td>
      </tr>
    ))
  )}
</tbody>
```

In JSX, `colspan` is written `colSpan`. While the rows load, you can show a few rows of [placeholders](/react/docs/components/placeholders/), which are part of AdminKit PRO.

## Responsive tables

On a small screen, a wide table would push the whole page sideways. Wrap it in `.table-responsive`, and the table scrolls on its own instead. The demo also makes the scrolling region reachable by keyboard and names it after the card title:

```tsx
<div className="card">
  <div className="card-header">
    <h2 className="card-title mb-0" id="usage-title">Usage by region</h2>
  </div>
  <div className="table-responsive" role="region" tabIndex={0} aria-labelledby="usage-title">
    <table className="table mb-0">{/* ... */}</table>
  </div>
</div>
```

To hide a column on small screens, give its `<th>` and `<td>` cells `d-none d-md-table-cell`, as the demo does for the date of birth.

## Accessibility

- Put the column headings in `<th scope="col">` and a row heading in `<th scope="row">`.
- Icon-only links in an actions cell need an `aria-label` that names the row, such as "Edit Vanessa Tucker".
- A scrolling `.table-responsive` gets `role="region"`, `tabIndex={0}` and a name, so keyboard users can scroll it.
- Do not mark a row with color alone: a `table-danger` row also needs a word, such as "Overdue".

## Related

- [Next.js Bootstrap tables](/nextjs/docs/components/tables/): the same markup in AdminKit for Next.js
- [Bootstrap 5 tables](/bootstrap/docs/components/tables/): the tables of the HTML version
- [Data table](/react/docs/plugins/data-table/): sorting, search and paging (PRO)
- [Tables demo](https://react-demo.adminkit.io/tables-bootstrap)

---

Canonical HTML page: https://adminkit.io/react/docs/components/tables/

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
