Open demo

CSS code

HTML
<h2 class="xp-title" id="xp-title">Expense claims</h2>
<p class="xp-sub">Click a row to expand its detail line — an accessible accordion inside a table.</p>
<div class="xp-card" role="region" tabindex="0" aria-label="Expense claims, scrollable">
<table class="xp" aria-labelledby="xp-title">
  <thead><tr><th scope="col">Claim</th><th scope="col">Employee</th><th scope="col">Amount</th><th scope="col">Date</th></tr></thead>
  <tbody>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" aria-expanded="false" aria-controls="xp-311"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-311</button></th><td>Sofia Marino</td><td class="xp-amount">$482.90</td><td>Jun 27</td></tr>
    <tr class="xp-detail" id="xp-311"><td colspan="4"><div class="xp-inner"><div><p><b>Client visit, Milan.</b> Train fare $198.00 · Hotel one night $214.90 · Meals $70.00. Approved by L. Farrell. Receipts attached (3).</p></div></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" aria-expanded="false" aria-controls="xp-310"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-310</button></th><td>Ben Okafor</td><td class="xp-amount">$96.20</td><td>Jun 25</td></tr>
    <tr class="xp-detail" id="xp-310"><td colspan="4"><div class="xp-inner"><div><p><b>Team offsite supplies.</b> Whiteboard markers, sticky notes, and adapters from OfficeMart. Card ending 4417. Receipts attached (1).</p></div></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" aria-expanded="false" aria-controls="xp-309"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-309</button></th><td>Clara Nilsen</td><td class="xp-amount">$1,240.00</td><td>Jun 22</td></tr>
    <tr class="xp-detail" id="xp-309"><td colspan="4"><div class="xp-inner"><div><p><b>Conference ticket, WebSummit.</b> Early-bird pass plus workshop add-on. Pending manager approval — flagged over the $1,000 single-item limit.</p></div></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" aria-expanded="false" aria-controls="xp-308"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-308</button></th><td>Igor Petrov</td><td class="xp-amount">$58.75</td><td>Jun 20</td></tr>
    <tr class="xp-detail" id="xp-308"><td colspan="4"><div class="xp-inner"><div><p><b>Client lunch.</b> Two covers at Verde. Project code NW-114. Approved automatically under the $75 meal threshold.</p></div></div></td></tr>
  </tbody>
</table>
</div>
CSS
/* Expandable rows: each claim number is a button with aria-expanded that opens the detail row below it.
   Clicking anywhere on the row does the same. The detail animates open with grid-template-rows 0fr -> 1fr,
   and stays visibility:hidden while closed so screen readers skip it. The card is a scroll region for narrow screens. */
.xp-title{font-size:1.15rem;font-weight:700;letter-spacing:-.01em;margin:0 0 4px}
.xp-sub{color:#6f6353;font-size:.87rem;margin:0 0 20px}
.xp-card{overflow-x:auto;background:#fff;border:1px solid #ece5da;border-radius:14px;box-shadow:0 12px 28px -18px rgba(74,58,36,.18)}
.xp-card:focus-visible{outline:2px solid #b45309;outline-offset:2px}
.xp{width:100%;border-collapse:collapse;font-size:.9rem}
.xp th,.xp td{text-align:left}
.xp thead th{white-space:nowrap;font-size:.73rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#6f6353;padding:12px 18px;background:#faf6f0;border-bottom:1px solid #ece5da}
.xp tbody th,.xp td{padding:13px 18px;border-top:1px solid #f3eee6;color:#4a3d2e}
.xp-row{cursor:pointer;transition:background-color .12s}
.xp-row:hover{background:#fbf7f1}
.xp-row th{font-weight:600}
.xp-toggle{display:inline-flex;white-space:nowrap;align-items:center;gap:10px;padding:0;border:0;background:none;font:inherit;font-weight:600;color:#2a2118;cursor:pointer;border-radius:4px}
.xp-toggle:focus-visible{outline:2px solid #b45309;outline-offset:3px}
.xp-chev{width:10px;height:10px;fill:#9c8b72;transition:transform .2s ease}
.xp-toggle[aria-expanded="true"] .xp-chev{transform:rotate(90deg)}
.xp-amount{font-variant-numeric:tabular-nums;font-weight:600}
.xp-detail>td{padding:0;border-top:0}
.xp-inner{display:grid;grid-template-rows:0fr;visibility:hidden;transition:grid-template-rows .25s ease,visibility .25s}
.xp-detail.open .xp-inner{grid-template-rows:1fr;visibility:visible}
.xp-inner>div{overflow:hidden;min-height:0}
.xp-inner p{margin:0;padding:16px 18px 18px 46px;background:#faf6ef;border-top:1px dashed #e7ddcd;font-size:.85rem;line-height:1.65;color:#6d5e4b}
.xp-inner b{color:#2a2118}
@media (max-width:480px){.xp thead th,.xp tbody th,.xp td{padding-left:10px;padding-right:10px}.xp .xp-detail>td{padding:0}.xp-inner p{padding-left:30px;padding-right:10px}}
@media (prefers-reduced-motion:reduce){.xp-row,.xp-chev,.xp-inner{transition:none}}
JavaScript
// Each toggle button opens the detail row named in its aria-controls; a click anywhere else on the row presses the button.
document.querySelectorAll('.xp-toggle').forEach(function (btn) {
  var row = btn.closest('tr');
  var detail = document.getElementById(btn.getAttribute('aria-controls'));
  if (!row || !detail) return;
  btn.addEventListener('click', function () {
    var open = btn.getAttribute('aria-expanded') !== 'true';
    btn.setAttribute('aria-expanded', String(open));
    detail.classList.toggle('open', open);
  });
  row.addEventListener('click', function (e) {
    if (!e.target.closest('button, a, input')) btn.click();
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<h2 class="xp-title" id="xp-title">Expense claims</h2>
<p class="xp-sub">Click a row to expand its detail line — an accessible accordion inside a table.</p>
<div class="xp-card table-responsive" role="region" tabindex="0" aria-label="Expense claims, scrollable">
<table class="table xp align-middle mb-0" aria-labelledby="xp-title">
  <thead><tr><th scope="col">Claim</th><th scope="col">Employee</th><th scope="col">Amount</th><th scope="col">Date</th></tr></thead>
  <tbody>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#xp-311" aria-expanded="false" aria-controls="xp-311"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-311</button></th><td>Sofia Marino</td><td class="xp-amount">$482.90</td><td>Jun 27</td></tr>
    <tr class="xp-detail"><td colspan="4"><div class="collapse" id="xp-311"><p><b>Client visit, Milan.</b> Train fare $198.00 · Hotel one night $214.90 · Meals $70.00. Approved by L. Farrell. Receipts attached (3).</p></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#xp-310" aria-expanded="false" aria-controls="xp-310"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-310</button></th><td>Ben Okafor</td><td class="xp-amount">$96.20</td><td>Jun 25</td></tr>
    <tr class="xp-detail"><td colspan="4"><div class="collapse" id="xp-310"><p><b>Team offsite supplies.</b> Whiteboard markers, sticky notes, and adapters from OfficeMart. Card ending 4417. Receipts attached (1).</p></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#xp-309" aria-expanded="false" aria-controls="xp-309"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-309</button></th><td>Clara Nilsen</td><td class="xp-amount">$1,240.00</td><td>Jun 22</td></tr>
    <tr class="xp-detail"><td colspan="4"><div class="collapse" id="xp-309"><p><b>Conference ticket, WebSummit.</b> Early-bird pass plus workshop add-on. Pending manager approval — flagged over the $1,000 single-item limit.</p></div></td></tr>
    <tr class="xp-row"><th scope="row"><button class="xp-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#xp-308" aria-expanded="false" aria-controls="xp-308"><svg class="xp-chev" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 0l7 5-7 5z"/></svg>EXP-308</button></th><td>Igor Petrov</td><td class="xp-amount">$58.75</td><td>Jun 20</td></tr>
    <tr class="xp-detail"><td colspan="4"><div class="collapse" id="xp-308"><p><b>Client lunch.</b> Two covers at Verde. Project code NW-114. Approved automatically under the $75 meal threshold.</p></div></td></tr>
  </tbody>
</table>
</div>
CSS
/* Bootstrap .table whose detail rows are .collapse panels, opened by a button with data-bs-toggle="collapse".
   The Collapse plugin keeps aria-expanded in sync and hides closed panels with display:none. */
.xp-title{font-size:1.15rem;font-weight:700;letter-spacing:-.01em;line-height:normal;margin:0 0 4px}
.xp-sub{color:#6f6353;font-size:.87rem;margin:0 0 20px}
.xp-card{background:#fff;border:1px solid #ece5da;border-radius:14px;box-shadow:0 12px 28px -18px rgba(74,58,36,.18)}
.xp-card:focus-visible{outline:2px solid #b45309;outline-offset:2px}
.xp{--bs-table-bg:#fff;--bs-table-color:#4a3d2e;--bs-table-border-color:#f3eee6;font-size:.9rem}
.xp>:not(caption)>*>*{border-bottom-width:0}
.xp>thead>tr>th{--bs-table-bg:#faf6f0;--bs-table-color:#6f6353;font-size:.73rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;padding:12px 18px;border-bottom:1px solid #ece5da}
.xp>tbody>.xp-row>*{padding:13px 18px;border-top:1px solid var(--bs-table-border-color)}
.xp-row{cursor:pointer}
.xp-row:hover>*{--bs-table-bg-state:#fbf7f1}
.xp-row th{font-weight:600}
.xp-toggle{display:inline-flex;white-space:nowrap;align-items:center;gap:10px;padding:0;border:0;background:none;font:inherit;font-weight:600;color:#2a2118;border-radius:4px}
.xp-toggle:focus-visible{outline:2px solid #b45309;outline-offset:3px}
.xp-chev{width:10px;height:10px;fill:#9c8b72;transition:transform .2s ease}
.xp-toggle[aria-expanded="true"] .xp-chev{transform:rotate(90deg)}
.xp-amount{font-variant-numeric:tabular-nums;font-weight:600}
.xp>tbody>.xp-detail>td{padding:0}
.xp-detail p{margin:0;padding:16px 18px 18px 46px;background:#faf6ef;border-top:1px dashed #e7ddcd;font-size:.85rem;line-height:1.65;color:#6d5e4b}
.xp-detail b{color:#2a2118}
@media (max-width:480px){.xp>thead>tr>th,.xp>tbody>.xp-row>*{padding-left:10px;padding-right:10px}.xp-detail p{padding-left:30px;padding-right:10px}}
@media (prefers-reduced-motion:reduce){.xp-chev{transition:none}}
JavaScript
// Bootstrap's Collapse plugin handles the buttons; this only lets a click anywhere on the row press its button.
document.querySelectorAll('.xp-row').forEach(function (row) {
  var btn = row.querySelector('.xp-toggle');
  if (!btn) return;
  row.addEventListener('click', function (e) {
    if (!e.target.closest('button, a, input')) btn.click();
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<h2 id="xp-title" class="mb-1 text-[1.15rem] font-bold tracking-[-.01em]">Expense claims</h2>
<p class="mb-5 text-[.87rem] text-[#6f6353]">Click a row to expand its detail line — an accessible accordion inside a table.</p>
<div role="region" tabindex="0" aria-label="Expense claims, scrollable" class="overflow-x-auto rounded-[14px] border border-[#ece5da] bg-white shadow-[0_12px_28px_-18px_rgba(74,58,36,.18)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#b45309]">
<table aria-labelledby="xp-title" class="w-full border-collapse text-left text-[.9rem]">
  <thead class="bg-[#faf6f0] text-[.73rem] font-bold tracking-[.06em] whitespace-nowrap text-[#6f6353] uppercase [&_th]:border-b [&_th]:border-[#ece5da] [&_th]:px-[18px] [&_th]:py-3 max-[480px]:[&_th]:px-2.5"><tr><th scope="col">Claim</th><th scope="col">Employee</th><th scope="col">Amount</th><th scope="col">Date</th></tr></thead>
  <tbody class="text-[#4a3d2e] [&>tr>*]:border-t [&>tr>*]:border-[#f3eee6] [&>tr>*]:px-[18px] [&>tr>*]:py-[13px] max-[480px]:[&>tr>*]:px-2.5">
    <tr class="cursor-pointer transition-colors duration-[120ms] hover:bg-[#fbf7f1] motion-reduce:transition-none"><th scope="row"><button type="button" aria-expanded="false" aria-controls="xp-311" data-xp-toggle class="group inline-flex cursor-pointer whitespace-nowrap items-center gap-2.5 rounded font-semibold text-[#2a2118] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#b45309]"><svg viewBox="0 0 10 10" aria-hidden="true" class="size-2.5 fill-[#9c8b72] transition-transform duration-200 group-aria-expanded:rotate-90 motion-reduce:transition-none"><path d="M2 0l7 5-7 5z"/></svg>EXP-311</button></th><td>Sofia Marino</td><td class="font-semibold tabular-nums">$482.90</td><td>Jun 27</td></tr>
    <tr id="xp-311" class="group"><td colspan="4" class="border-t-0! p-0!"><div class="invisible grid grid-rows-[0fr] transition-[grid-template-rows,visibility] duration-[250ms] group-data-open:visible group-data-open:grid-rows-[1fr] motion-reduce:transition-none"><div class="min-h-0 overflow-hidden"><p class="border-t border-dashed border-[#e7ddcd] bg-[#faf6ef] py-4 pr-[18px] pb-[18px] pl-[46px] text-[.85rem] max-[480px]:pr-2.5 max-[480px]:pl-[30px] leading-[1.65] text-[#6d5e4b] [&_b]:text-[#2a2118]"><b>Client visit, Milan.</b> Train fare $198.00 · Hotel one night $214.90 · Meals $70.00. Approved by L. Farrell. Receipts attached (3).</p></div></div></td></tr>
    <tr class="cursor-pointer transition-colors duration-[120ms] hover:bg-[#fbf7f1] motion-reduce:transition-none"><th scope="row"><button type="button" aria-expanded="false" aria-controls="xp-310" data-xp-toggle class="group inline-flex cursor-pointer whitespace-nowrap items-center gap-2.5 rounded font-semibold text-[#2a2118] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#b45309]"><svg viewBox="0 0 10 10" aria-hidden="true" class="size-2.5 fill-[#9c8b72] transition-transform duration-200 group-aria-expanded:rotate-90 motion-reduce:transition-none"><path d="M2 0l7 5-7 5z"/></svg>EXP-310</button></th><td>Ben Okafor</td><td class="font-semibold tabular-nums">$96.20</td><td>Jun 25</td></tr>
    <tr id="xp-310" class="group"><td colspan="4" class="border-t-0! p-0!"><div class="invisible grid grid-rows-[0fr] transition-[grid-template-rows,visibility] duration-[250ms] group-data-open:visible group-data-open:grid-rows-[1fr] motion-reduce:transition-none"><div class="min-h-0 overflow-hidden"><p class="border-t border-dashed border-[#e7ddcd] bg-[#faf6ef] py-4 pr-[18px] pb-[18px] pl-[46px] text-[.85rem] max-[480px]:pr-2.5 max-[480px]:pl-[30px] leading-[1.65] text-[#6d5e4b] [&_b]:text-[#2a2118]"><b>Team offsite supplies.</b> Whiteboard markers, sticky notes, and adapters from OfficeMart. Card ending 4417. Receipts attached (1).</p></div></div></td></tr>
    <tr class="cursor-pointer transition-colors duration-[120ms] hover:bg-[#fbf7f1] motion-reduce:transition-none"><th scope="row"><button type="button" aria-expanded="false" aria-controls="xp-309" data-xp-toggle class="group inline-flex cursor-pointer whitespace-nowrap items-center gap-2.5 rounded font-semibold text-[#2a2118] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#b45309]"><svg viewBox="0 0 10 10" aria-hidden="true" class="size-2.5 fill-[#9c8b72] transition-transform duration-200 group-aria-expanded:rotate-90 motion-reduce:transition-none"><path d="M2 0l7 5-7 5z"/></svg>EXP-309</button></th><td>Clara Nilsen</td><td class="font-semibold tabular-nums">$1,240.00</td><td>Jun 22</td></tr>
    <tr id="xp-309" class="group"><td colspan="4" class="border-t-0! p-0!"><div class="invisible grid grid-rows-[0fr] transition-[grid-template-rows,visibility] duration-[250ms] group-data-open:visible group-data-open:grid-rows-[1fr] motion-reduce:transition-none"><div class="min-h-0 overflow-hidden"><p class="border-t border-dashed border-[#e7ddcd] bg-[#faf6ef] py-4 pr-[18px] pb-[18px] pl-[46px] text-[.85rem] max-[480px]:pr-2.5 max-[480px]:pl-[30px] leading-[1.65] text-[#6d5e4b] [&_b]:text-[#2a2118]"><b>Conference ticket, WebSummit.</b> Early-bird pass plus workshop add-on. Pending manager approval — flagged over the $1,000 single-item limit.</p></div></div></td></tr>
    <tr class="cursor-pointer transition-colors duration-[120ms] hover:bg-[#fbf7f1] motion-reduce:transition-none"><th scope="row"><button type="button" aria-expanded="false" aria-controls="xp-308" data-xp-toggle class="group inline-flex cursor-pointer whitespace-nowrap items-center gap-2.5 rounded font-semibold text-[#2a2118] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#b45309]"><svg viewBox="0 0 10 10" aria-hidden="true" class="size-2.5 fill-[#9c8b72] transition-transform duration-200 group-aria-expanded:rotate-90 motion-reduce:transition-none"><path d="M2 0l7 5-7 5z"/></svg>EXP-308</button></th><td>Igor Petrov</td><td class="font-semibold tabular-nums">$58.75</td><td>Jun 20</td></tr>
    <tr id="xp-308" class="group"><td colspan="4" class="border-t-0! p-0!"><div class="invisible grid grid-rows-[0fr] transition-[grid-template-rows,visibility] duration-[250ms] group-data-open:visible group-data-open:grid-rows-[1fr] motion-reduce:transition-none"><div class="min-h-0 overflow-hidden"><p class="border-t border-dashed border-[#e7ddcd] bg-[#faf6ef] py-4 pr-[18px] pb-[18px] pl-[46px] text-[.85rem] max-[480px]:pr-2.5 max-[480px]:pl-[30px] leading-[1.65] text-[#6d5e4b] [&_b]:text-[#2a2118]"><b>Client lunch.</b> Two covers at Verde. Project code NW-114. Approved automatically under the $75 meal threshold.</p></div></div></td></tr>
  </tbody>
</table>
</div>
JavaScript
// Each toggle opens the detail row named in its aria-controls (data-open drives the group-data-open: utilities);
// a click anywhere else on the row presses the button.
document.querySelectorAll('[data-xp-toggle]').forEach(function (btn) {
  var row = btn.closest('tr');
  var detail = document.getElementById(btn.getAttribute('aria-controls'));
  if (!row || !detail) return;
  btn.addEventListener('click', function () {
    var open = btn.getAttribute('aria-expanded') !== 'true';
    btn.setAttribute('aria-expanded', String(open));
    detail.toggleAttribute('data-open', open);
  });
  row.addEventListener('click', function (e) {
    if (!e.target.closest('button, a, input')) btn.click();
  });
});

About this set

An expense claims table where each row opens a detail line underneath it, an accordion built inside a table. The claim number in the first cell is a real button with aria-expanded and aria-controls pointing at its detail row, and a click anywhere else on the row presses that button, so mouse users get a large target while keyboard and screen reader users get a proper disclosure control. A small triangle rotates 90 degrees when the row is open. The detail row spans all four columns and holds a short description of the claim. In the plain CSS and Tailwind versions it animates open by moving grid-template-rows from 0fr to 1fr, which fits text of any length without a fixed max-height, and it stays visibility:hidden while closed so its text is not read out. The Bootstrap version uses the Collapse plugin with data-bs-toggle=”collapse”, which keeps aria-expanded in sync for you. Use it for orders, transactions and support tickets where the extra detail is useful but would crowd the main table.

What’s included

  • Disclosure buttons with aria-expanded and aria-controls
  • Whole row is clickable, the button handles the keyboard
  • Height animation with grid-template-rows, no fixed max-height
  • Closed details hidden from screen readers
  • Bootstrap Collapse plugin version

Accessibility

Each claim number is a button that reports its expanded state and controls the detail row below. Closed details are visibility:hidden (display:none in Bootstrap), so they are skipped until opened. Buttons show a 2px amber focus outline, and the chevron, row and height transitions are removed under prefers-reduced-motion.

Customise it

Colours are in the .xp rules: header #faf6f0, detail background #faf6ef, chevron #9c8b72, focus #b45309; the open speed is the .25s on .xp-inner. Bootstrap uses --bs-table-* variables on .xp; in Tailwind edit duration-[250ms] and the colour utilities.