Open demo

CSS code

HTML
<div class="rsc">
  <h2>Meeting rooms · Tuesday 15 September</h2>
  <p class="sub">Four rooms · 09:00 to 19:00</p>
  <div class="scroll" tabindex="0" role="region" aria-label="Room bookings, scrolls sideways">
    <table class="g">
      <caption class="sr-only">Room bookings by hour, Tuesday 15 September</caption>
      <thead><tr><th scope="col"><span class="sr-only">Room</span></th><th scope="col">09<span class="sr-only">:00</span></th><th scope="col">10<span class="sr-only">:00</span></th><th scope="col">11<span class="sr-only">:00</span></th><th scope="col">12<span class="sr-only">:00</span></th><th scope="col">13<span class="sr-only">:00</span></th><th scope="col">14<span class="sr-only">:00</span></th><th scope="col">15<span class="sr-only">:00</span></th><th scope="col">16<span class="sr-only">:00</span></th><th scope="col">17<span class="sr-only">:00</span></th><th scope="col">18<span class="sr-only">:00</span></th></tr></thead>
      <tbody>
        <tr><th scope="row">Boardroom <small><span class="sr-only">, seats </span>12</small></th><td colspan="2"><span class="bk" style="--c:#6d28d9">Q4 planning<span class="sr-only">, 09:00 to 11:00, internal</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td colspan="2"><span class="bk" style="--c:#0369a1">Client — Meridian<span class="sr-only">, 14:00 to 16:00, external</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row">Studio A <small><span class="sr-only">, seats </span>6</small></th><td><span class="sr-only">Free</span></td><td colspan="3"><span class="bk" style="--c:#15803d">Photo shoot<span class="sr-only">, 10:00 to 13:00, production</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td colspan="2"><span class="bk" style="--c:#b45309">Editing<span class="sr-only">, 16:00 to 18:00, post-production</span></span></td><td><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row">Focus 1 <small><span class="sr-only">, seats </span>2</small></th><td><span class="bk" style="--c:#e11d48">1:1<span class="sr-only">, 09:00 to 10:00, one to one</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="bk" style="--c:#e11d48">1:1<span class="sr-only">, 12:00 to 13:00, one to one</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="bk" style="--c:#e11d48">1:1<span class="sr-only">, 15:00 to 16:00, one to one</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row">Focus 2 <small><span class="sr-only">, seats </span>2</small></th><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td colspan="2"><span class="bk" style="--c:#0369a1">Interviews<span class="sr-only">, 13:00 to 15:00, external</span></span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td><td><span class="sr-only">Free</span></td></tr>
      </tbody>
    </table>
  </div>
  <ul class="key">
    <li><i aria-hidden="true" style="background:#6d28d9"></i> Internal</li>
    <li><i aria-hidden="true" style="background:#0369a1"></i> External</li>
    <li><i aria-hidden="true" style="background:#15803d"></i> Production</li>
    <li><i aria-hidden="true" style="background:#b45309"></i> Post-production</li>
    <li><i aria-hidden="true" style="background:#e11d48"></i> One to one</li>
  </ul>
</div>
CSS
/* Resource scheduling: a real table, rooms as row headers and hours as column headers.
   A booking is one cell spanning its hours (colspan); free hours say "Free" to screen readers. */
.rsc{--rs-muted:#5f6b7c;--rs-line:#eef1f5;
  box-sizing:border-box;width:100%;max-width:880px;padding:22px 24px 24px;background:#fff;border-radius:16px;
  box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.rsc h2{font-size:.98rem;margin:0 0 2px}
.rsc .sub{font-size:.79rem;color:var(--rs-muted);margin:0 0 18px}
.rsc .scroll{position:relative;overflow-x:auto;border-radius:6px}
.rsc .scroll:focus-visible{outline:2px solid #6d28d9;outline-offset:2px}
.rsc table{width:100%;min-width:700px;border-collapse:collapse;table-layout:fixed}
.rsc thead th{font-size:.68rem;font-weight:800;color:var(--rs-muted);text-align:center;padding:0 0 8px}
.rsc thead th:first-child{width:118px}
.rsc tbody th{font-size:.84rem;font-weight:600;text-align:left;padding:4px 12px 4px 0;white-space:nowrap}
.rsc tbody th small{font-size:.68rem;color:var(--rs-muted);font-weight:500;margin-left:4px}
.rsc td{border-left:1px solid var(--rs-line);padding:4px 0;height:38px}
.rsc .bk{display:flex;align-items:center;height:30px;margin:0 3px;padding:0 9px;border-radius:7px;background:var(--c);
  font-size:.71rem;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden}
.rsc .key{display:flex;gap:16px;margin:18px 0 0;padding:0;list-style:none;font-size:.76rem;color:var(--rs-muted);flex-wrap:wrap}
.rsc .key li{display:flex;align-items:center;gap:6px}
.rsc .key i{width:10px;height:10px;border-radius:3px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="rsc card w-100">
  <h2 class="fw-bold mb-1" style="font-size:.98rem">Meeting rooms · Tuesday 15 September</h2>
  <p class="mb-3" style="font-size:.79rem;color:var(--rs-muted)">Four rooms · 09:00 to 19:00</p>
  <div class="scroll overflow-x-auto" tabindex="0" role="region" aria-label="Room bookings, scrolls sideways">
    <table class="table align-middle mb-0">
      <caption class="visually-hidden">Room bookings by hour, Tuesday 15 September</caption>
      <thead><tr><th scope="col"><span class="visually-hidden">Room</span></th><th scope="col">09<span class="visually-hidden">:00</span></th><th scope="col">10<span class="visually-hidden">:00</span></th><th scope="col">11<span class="visually-hidden">:00</span></th><th scope="col">12<span class="visually-hidden">:00</span></th><th scope="col">13<span class="visually-hidden">:00</span></th><th scope="col">14<span class="visually-hidden">:00</span></th><th scope="col">15<span class="visually-hidden">:00</span></th><th scope="col">16<span class="visually-hidden">:00</span></th><th scope="col">17<span class="visually-hidden">:00</span></th><th scope="col">18<span class="visually-hidden">:00</span></th></tr></thead>
      <tbody>
        <tr><th scope="row">Boardroom <small><span class="visually-hidden">, seats </span>12</small></th><td colspan="2"><span class="bk" style="--c:#6d28d9">Q4 planning<span class="visually-hidden">, 09:00 to 11:00, internal</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td colspan="2"><span class="bk" style="--c:#0369a1">Client — Meridian<span class="visually-hidden">, 14:00 to 16:00, external</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td></tr>
        <tr><th scope="row">Studio A <small><span class="visually-hidden">, seats </span>6</small></th><td><span class="visually-hidden">Free</span></td><td colspan="3"><span class="bk" style="--c:#15803d">Photo shoot<span class="visually-hidden">, 10:00 to 13:00, production</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td colspan="2"><span class="bk" style="--c:#b45309">Editing<span class="visually-hidden">, 16:00 to 18:00, post-production</span></span></td><td><span class="visually-hidden">Free</span></td></tr>
        <tr><th scope="row">Focus 1 <small><span class="visually-hidden">, seats </span>2</small></th><td><span class="bk" style="--c:#e11d48">1:1<span class="visually-hidden">, 09:00 to 10:00, one to one</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="bk" style="--c:#e11d48">1:1<span class="visually-hidden">, 12:00 to 13:00, one to one</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="bk" style="--c:#e11d48">1:1<span class="visually-hidden">, 15:00 to 16:00, one to one</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td></tr>
        <tr><th scope="row">Focus 2 <small><span class="visually-hidden">, seats </span>2</small></th><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td colspan="2"><span class="bk" style="--c:#0369a1">Interviews<span class="visually-hidden">, 13:00 to 15:00, external</span></span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td><td><span class="visually-hidden">Free</span></td></tr>
      </tbody>
    </table>
  </div>
  <ul class="key list-unstyled d-flex flex-wrap gap-3 mt-3 mb-0" style="font-size:.76rem;color:var(--rs-muted)">
    <li class="d-flex align-items-center gap-2"><i aria-hidden="true" style="background:#6d28d9"></i> Internal</li>
    <li class="d-flex align-items-center gap-2"><i aria-hidden="true" style="background:#0369a1"></i> External</li>
    <li class="d-flex align-items-center gap-2"><i aria-hidden="true" style="background:#15803d"></i> Production</li>
    <li class="d-flex align-items-center gap-2"><i aria-hidden="true" style="background:#b45309"></i> Post-production</li>
    <li class="d-flex align-items-center gap-2"><i aria-hidden="true" style="background:#e11d48"></i> One to one</li>
  </ul>
</div>
CSS
/* Bootstrap card and .table, restyled through --bs-table-* variables; the booking block is custom. */
.rsc{--rs-muted:#5f6b7c;--rs-line:#eef1f5;
  max-width:880px;padding:22px 24px 24px;border:0;border-radius:16px;box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.rsc .scroll{position:relative;border-radius:6px}
.rsc .scroll:focus-visible{outline:2px solid #6d28d9;outline-offset:2px}
.rsc .table{--bs-table-bg:transparent;min-width:700px;table-layout:fixed}
.rsc .table>:not(caption)>*>*{border-bottom:0;padding:4px 0}
.rsc .table thead th{font-size:.68rem;font-weight:800;color:var(--rs-muted);text-align:center;padding:0 0 8px}
.rsc .table thead th:first-child{width:118px}
.rsc .table tbody th{font-size:.84rem;font-weight:600;padding-right:12px;white-space:nowrap}
.rsc .table tbody th small{font-size:.68rem;color:var(--rs-muted);font-weight:500;margin-left:4px}
.rsc .table td{border-left:1px solid var(--rs-line);height:38px}
.rsc .bk{display:flex;align-items:center;height:30px;margin:0 3px;padding:0 9px;border-radius:7px;background:var(--c);
  font-size:.71rem;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden}
.rsc .key i{width:10px;height:10px;border-radius:3px}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="rsc w-full max-w-[880px] rounded-2xl bg-white px-6 pt-[22px] pb-6 shadow-[0_14px_34px_-18px_rgba(18,24,40,.25)]">
  <h2 class="mb-0.5 text-[.98rem] font-bold">Meeting rooms · Tuesday 15 September</h2>
  <p class="mb-[18px] text-[.79rem] text-rs-muted">Four rooms · 09:00 to 19:00</p>
  <div class="relative overflow-x-auto rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9]" tabindex="0" role="region" aria-label="Room bookings, scrolls sideways">
    <table class="w-full min-w-[700px] table-fixed border-collapse [&_thead_th]:pb-2 [&_thead_th]:text-center [&_thead_th]:text-[.68rem] [&_thead_th]:font-extrabold [&_thead_th]:text-rs-muted [&_thead_th:first-child]:w-[118px]">
      <caption class="sr-only">Room bookings by hour, Tuesday 15 September</caption>
      <thead><tr><th scope="col"><span class="sr-only">Room</span></th><th scope="col">09<span class="sr-only">:00</span></th><th scope="col">10<span class="sr-only">:00</span></th><th scope="col">11<span class="sr-only">:00</span></th><th scope="col">12<span class="sr-only">:00</span></th><th scope="col">13<span class="sr-only">:00</span></th><th scope="col">14<span class="sr-only">:00</span></th><th scope="col">15<span class="sr-only">:00</span></th><th scope="col">16<span class="sr-only">:00</span></th><th scope="col">17<span class="sr-only">:00</span></th><th scope="col">18<span class="sr-only">:00</span></th></tr></thead>
      <tbody>
        <tr><th scope="row" class="whitespace-nowrap py-1 pr-3 text-left text-[.84rem] font-semibold">Boardroom <small class="ml-1 text-[.68rem] font-medium text-rs-muted"><span class="sr-only">, seats </span>12</small></th><td colspan="2" class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#6d28d9">Q4 planning<span class="sr-only">, 09:00 to 11:00, internal</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td colspan="2" class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#0369a1">Client — Meridian<span class="sr-only">, 14:00 to 16:00, external</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row" class="whitespace-nowrap py-1 pr-3 text-left text-[.84rem] font-semibold">Studio A <small class="ml-1 text-[.68rem] font-medium text-rs-muted"><span class="sr-only">, seats </span>6</small></th><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td colspan="3" class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#15803d">Photo shoot<span class="sr-only">, 10:00 to 13:00, production</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td colspan="2" class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#b45309">Editing<span class="sr-only">, 16:00 to 18:00, post-production</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row" class="whitespace-nowrap py-1 pr-3 text-left text-[.84rem] font-semibold">Focus 1 <small class="ml-1 text-[.68rem] font-medium text-rs-muted"><span class="sr-only">, seats </span>2</small></th><td class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#e11d48">1:1<span class="sr-only">, 09:00 to 10:00, one to one</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#e11d48">1:1<span class="sr-only">, 12:00 to 13:00, one to one</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#e11d48">1:1<span class="sr-only">, 15:00 to 16:00, one to one</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td></tr>
        <tr><th scope="row" class="whitespace-nowrap py-1 pr-3 text-left text-[.84rem] font-semibold">Focus 2 <small class="ml-1 text-[.68rem] font-medium text-rs-muted"><span class="sr-only">, seats </span>2</small></th><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td colspan="2" class="h-[38px] border-l border-rs-line py-1"><span class="mx-[3px] flex h-[30px] items-center overflow-hidden rounded-[7px] bg-(--c) px-[9px] text-[.71rem] font-bold whitespace-nowrap text-white" style="--c:#0369a1">Interviews<span class="sr-only">, 13:00 to 15:00, external</span></span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td><td class="h-[38px] border-l border-rs-line py-1"><span class="sr-only">Free</span></td></tr>
      </tbody>
    </table>
  </div>
  <ul class="mt-[18px] flex flex-wrap gap-4 text-[.76rem] text-rs-muted">
    <li class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px]" style="background:#6d28d9"></i> Internal</li>
    <li class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px]" style="background:#0369a1"></i> External</li>
    <li class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px]" style="background:#15803d"></i> Production</li>
    <li class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px]" style="background:#b45309"></i> Post-production</li>
    <li class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px]" style="background:#e11d48"></i> One to one</li>
  </ul>
</div>
CSS
@theme {
  --color-rs-muted: #5f6b7c;
  --color-rs-line: #eef1f5;
}

About this set

A resource view for one day: four meeting rooms down the side, the hours from 09:00 to 19:00 across the top and each booking drawn as a coloured bar across the hours it takes. A key below names the five booking types. Use it for room booking, equipment hire, staff rotas or any schedule where several resources share the same hours. The grid is a real HTML table: rooms are row headers with their seat count, hours are column headers, and a booking is a single cell with a colspan equal to its length. Because of that, a screen reader can move along a row and hear each booking with its time range and type, and the empty hours say Free, so availability does not depend on spotting a blank cell. The table has a minimum width of 700 pixels and sits in a scrollable region, so on a phone it scrolls sideways instead of squeezing the labels; the region can be focused and scrolled with the keyboard. Booking colours were picked or darkened so the white labels reach 4.5:1 contrast. The Bootstrap version uses .table and the Tailwind version uses utilities on the same table.

What’s included

  • Real table with rooms as row headers and hours as column headers
  • Bookings span their hours with colspan
  • Free hours and booking type spelled out in hidden text
  • Keyboard-scrollable region below 700px
  • Plain CSS, Bootstrap .table and Tailwind versions of the same design

Accessibility

Rooms are row headers and hours are column headers, so each booking is announced with its room and hour. Every booking includes its time range and type as visually hidden text, and empty hours read "Free". The scroll region has tabindex="0", an aria-label and a 2px violet focus outline. Nothing animates.

Customise it

Set each booking colour with --c on the .bk element and edit the key to match. Change --rs-muted and --rs-line on .rsc in plain CSS and Bootstrap, or the --color-rs-* theme colours in Tailwind; the room column width is the 118px on the first header.