Open demo

CSS code

HTML
<div class="tslot">
  <h2>Book a 30-minute call</h2>
  <p class="sub">Pick a day, then a time that suits you.</p>
  <div class="days" role="radiogroup" aria-label="Choose a day">
    <input type="radio" name="day" id="ts-d0" value="2026-09-14" aria-label="Monday 14 September"><label for="ts-d0"><small>Mon</small><b>14</b></label>
    <input type="radio" name="day" id="ts-d1" value="2026-09-15" aria-label="Tuesday 15 September" checked><label for="ts-d1"><small>Tue</small><b>15</b></label>
    <input type="radio" name="day" id="ts-d2" value="2026-09-16" aria-label="Wednesday 16 September"><label for="ts-d2"><small>Wed</small><b>16</b></label>
    <input type="radio" name="day" id="ts-d3" value="2026-09-17" aria-label="Thursday 17 September"><label for="ts-d3"><small>Thu</small><b>17</b></label>
    <input type="radio" name="day" id="ts-d4" value="2026-09-18" aria-label="Friday 18 September"><label for="ts-d4"><small>Fri</small><b>18</b></label>
    <input type="radio" name="day" id="ts-d5" value="2026-09-21" aria-label="Monday 21 September"><label for="ts-d5"><small>Mon</small><b>21</b></label>
  </div>
  <div class="slots" role="radiogroup" aria-label="Choose a time">
    <input type="radio" name="time" id="ts-t0" value="09:00" aria-label="Tuesday 15 September, 09:00"><label for="ts-t0">09:00</label>
    <input type="radio" name="time" id="ts-t1" value="09:30" aria-label="Tuesday 15 September, 09:30, taken" disabled><label for="ts-t1">09:30</label>
    <input type="radio" name="time" id="ts-t2" value="10:00" aria-label="Tuesday 15 September, 10:00"><label for="ts-t2">10:00</label>
    <input type="radio" name="time" id="ts-t3" value="10:30" aria-label="Tuesday 15 September, 10:30" checked><label for="ts-t3">10:30</label>
    <input type="radio" name="time" id="ts-t4" value="11:00" aria-label="Tuesday 15 September, 11:00, taken" disabled><label for="ts-t4">11:00</label>
    <input type="radio" name="time" id="ts-t5" value="13:00" aria-label="Tuesday 15 September, 13:00"><label for="ts-t5">13:00</label>
    <input type="radio" name="time" id="ts-t6" value="13:30" aria-label="Tuesday 15 September, 13:30"><label for="ts-t6">13:30</label>
    <input type="radio" name="time" id="ts-t7" value="14:00" aria-label="Tuesday 15 September, 14:00"><label for="ts-t7">14:00</label>
    <input type="radio" name="time" id="ts-t8" value="15:00" aria-label="Tuesday 15 September, 15:00, taken" disabled><label for="ts-t8">15:00</label>
    <input type="radio" name="time" id="ts-t9" value="16:00" aria-label="Tuesday 15 September, 16:00"><label for="ts-t9">16:00</label>
  </div>
  <p class="tz"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Times shown in Europe/London (BST)</p>
</div>
CSS
/* Appointment slots: hidden radio inputs, the label after each one is the visible button. */
.tslot{--ts-accent:#6d28d9;--ts-line:#dbe1ea;--ts-muted:#5f6b7c;
  box-sizing:border-box;width:100%;max-width:520px;padding:22px 24px 24px;background:#fff;border-radius:16px;
  box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.tslot h2{font-size:1rem;margin:0 0 3px}
.tslot .sub{font-size:.8rem;color:var(--ts-muted);margin:0 0 18px}
.tslot .days{display:flex;gap:8px;overflow-x:auto;padding:2px 2px 6px;margin-bottom:20px}
.tslot input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.tslot .days label{flex:0 0 62px;border:1px solid var(--ts-line);border-radius:11px;padding:9px 0;text-align:center;
  cursor:pointer;transition:border-color .14s,background .14s}
.tslot .days small{display:block;font-size:.64rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--ts-muted)}
.tslot .days b{display:block;font-size:1.05rem;margin-top:2px}
.tslot .days label:hover{border-color:#c2cbd8}
.tslot .days input:checked + label{background:var(--ts-accent);border-color:var(--ts-accent)}
.tslot .days input:checked + label small{color:#e9e1fb}
.tslot .days input:checked + label b{color:#fff}
.tslot .slots{display:grid;grid-template-columns:repeat(auto-fill,minmax(94px,1fr));gap:9px;padding:2px}
.tslot .slots label{border:1px solid var(--ts-line);border-radius:10px;padding:10px 0;text-align:center;
  font-size:.85rem;font-weight:700;cursor:pointer;transition:border-color .14s,background .14s}
.tslot .slots label:hover{border-color:var(--ts-accent);color:var(--ts-accent)}
.tslot .slots input:checked + label{background:var(--ts-accent);border-color:var(--ts-accent);color:#fff}
.tslot .slots input:disabled + label{color:#667284;background:#f6f8fa;border-color:var(--ts-line);cursor:not-allowed;text-decoration:line-through}
.tslot input:focus-visible + label{outline:2px solid var(--ts-accent);outline-offset:2px}
.tslot .tz{margin:18px 0 0;font-size:.78rem;color:var(--ts-muted);display:flex;align-items:center;gap:7px}
.tslot .tz svg{display:block;width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2}
@media (prefers-reduced-motion:reduce){.tslot label{transition:none}}
JavaScript
/* Each time slot's accessible name carries the chosen day ("Tuesday 15 September, 10:30"),
   so a screen reader user hears the full appointment. Re-run when the day changes. */
document.querySelectorAll('.tslot').forEach(root => {
  const name = () => {
    const day = root.querySelector('[name=day]:checked').getAttribute('aria-label');
    root.querySelectorAll('[name=time]').forEach(t =>
      t.setAttribute('aria-label', day + ', ' + t.value + (t.disabled ? ', taken' : '')));
  };
  root.addEventListener('change', e => { if (e.target.name === 'day') name(); });
  name();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="tslot card w-100">
  <h2 class="fs-6 fw-bold mb-1">Book a 30-minute call</h2>
  <p class="mb-3 small" style="color:var(--ts-muted)">Pick a day, then a time that suits you.</p>
  <div class="days d-flex gap-2 overflow-x-auto mb-4" role="radiogroup" aria-label="Choose a day">
    <input type="radio" class="btn-check" name="day" id="ts-d0" value="2026-09-14" aria-label="Monday 14 September"><label class="btn btn-day" for="ts-d0"><small>Mon</small><b>14</b></label>
    <input type="radio" class="btn-check" name="day" id="ts-d1" value="2026-09-15" aria-label="Tuesday 15 September" checked><label class="btn btn-day" for="ts-d1"><small>Tue</small><b>15</b></label>
    <input type="radio" class="btn-check" name="day" id="ts-d2" value="2026-09-16" aria-label="Wednesday 16 September"><label class="btn btn-day" for="ts-d2"><small>Wed</small><b>16</b></label>
    <input type="radio" class="btn-check" name="day" id="ts-d3" value="2026-09-17" aria-label="Thursday 17 September"><label class="btn btn-day" for="ts-d3"><small>Thu</small><b>17</b></label>
    <input type="radio" class="btn-check" name="day" id="ts-d4" value="2026-09-18" aria-label="Friday 18 September"><label class="btn btn-day" for="ts-d4"><small>Fri</small><b>18</b></label>
    <input type="radio" class="btn-check" name="day" id="ts-d5" value="2026-09-21" aria-label="Monday 21 September"><label class="btn btn-day" for="ts-d5"><small>Mon</small><b>21</b></label>
  </div>
  <div class="slots" role="radiogroup" aria-label="Choose a time">
    <input type="radio" class="btn-check" name="time" id="ts-t0" value="09:00" aria-label="Tuesday 15 September, 09:00"><label class="btn btn-slot" for="ts-t0">09:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t1" value="09:30" aria-label="Tuesday 15 September, 09:30, taken" disabled><label class="btn btn-slot" for="ts-t1">09:30</label>
    <input type="radio" class="btn-check" name="time" id="ts-t2" value="10:00" aria-label="Tuesday 15 September, 10:00"><label class="btn btn-slot" for="ts-t2">10:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t3" value="10:30" aria-label="Tuesday 15 September, 10:30" checked><label class="btn btn-slot" for="ts-t3">10:30</label>
    <input type="radio" class="btn-check" name="time" id="ts-t4" value="11:00" aria-label="Tuesday 15 September, 11:00, taken" disabled><label class="btn btn-slot" for="ts-t4">11:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t5" value="13:00" aria-label="Tuesday 15 September, 13:00"><label class="btn btn-slot" for="ts-t5">13:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t6" value="13:30" aria-label="Tuesday 15 September, 13:30"><label class="btn btn-slot" for="ts-t6">13:30</label>
    <input type="radio" class="btn-check" name="time" id="ts-t7" value="14:00" aria-label="Tuesday 15 September, 14:00"><label class="btn btn-slot" for="ts-t7">14:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t8" value="15:00" aria-label="Tuesday 15 September, 15:00, taken" disabled><label class="btn btn-slot" for="ts-t8">15:00</label>
    <input type="radio" class="btn-check" name="time" id="ts-t9" value="16:00" aria-label="Tuesday 15 September, 16:00"><label class="btn btn-slot" for="ts-t9">16:00</label>
  </div>
  <p class="tz d-flex align-items-center gap-2 mt-3 mb-0" style="font-size:.78rem;color:var(--ts-muted)"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Times shown in Europe/London (BST)</p>
</div>
CSS
/* Bootstrap .btn-check + .btn for both radio groups, restyled through the --bs-btn-* variables. */
.tslot{--ts-accent:#6d28d9;--ts-line:#dbe1ea;--ts-muted:#5f6b7c;
  max-width:520px;padding:22px 24px 24px;border:0;border-radius:16px;box-shadow:0 14px 34px -18px rgba(18,24,40,.25)}
.tslot .days{padding:2px 2px 6px}
.tslot .btn{--bs-btn-border-color:var(--ts-line);--bs-btn-border-radius:10px;--bs-btn-color:#191e28;--bs-btn-font-weight:700;
  --bs-btn-hover-bg:transparent;--bs-btn-hover-border-color:var(--ts-accent);--bs-btn-hover-color:var(--ts-accent);
  --bs-btn-active-bg:var(--ts-accent);--bs-btn-active-border-color:var(--ts-accent);--bs-btn-active-color:#fff;
  --bs-btn-disabled-opacity:1;--bs-btn-disabled-color:#667284;--bs-btn-disabled-bg:#f6f8fa;--bs-btn-disabled-border-color:var(--ts-line);
  line-height:normal;transition:border-color .14s,background-color .14s}
.tslot .btn-day{--bs-btn-padding-x:0;--bs-btn-padding-y:9px;--bs-btn-border-radius:11px;--bs-btn-hover-border-color:#c2cbd8;--bs-btn-hover-color:#191e28;flex:0 0 62px}
.tslot .btn-day small{display:block;font-size:.64rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--ts-muted)}
.tslot .btn-day b{display:block;font-size:1.05rem;margin-top:2px}
.tslot .btn-check:checked + .btn-day small{color:#e9e1fb}
.tslot .slots{display:grid;grid-template-columns:repeat(auto-fill,minmax(94px,1fr));gap:9px;padding:2px}
.tslot .btn-slot{--bs-btn-padding-x:0;--bs-btn-padding-y:10px;--bs-btn-font-size:.85rem}
.tslot .btn-check:disabled + .btn{opacity:1;color:#667284;background:#f6f8fa;text-decoration:line-through;cursor:not-allowed;pointer-events:auto}
.tslot .btn-check:focus-visible + .btn{box-shadow:none;outline:2px solid var(--ts-accent);outline-offset:2px}
.tslot .tz svg{display:block;width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2}
@media (prefers-reduced-motion:reduce){.tslot .btn{transition:none}}
JavaScript
/* Each time slot's accessible name carries the chosen day ("Tuesday 15 September, 10:30"),
   so a screen reader user hears the full appointment. Re-run when the day changes. */
document.querySelectorAll('.tslot').forEach(root => {
  const name = () => {
    const day = root.querySelector('[name=day]:checked').getAttribute('aria-label');
    root.querySelectorAll('[name=time]').forEach(t =>
      t.setAttribute('aria-label', day + ', ' + t.value + (t.disabled ? ', taken' : '')));
  };
  root.addEventListener('change', e => { if (e.target.name === 'day') name(); });
  name();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="tslot w-full max-w-[520px] rounded-2xl bg-white px-6 pt-[22px] pb-6 shadow-[0_14px_34px_-18px_rgba(18,24,40,.25)]">
  <h2 class="mb-[3px] text-base font-bold">Book a 30-minute call</h2>
  <p class="mb-[18px] text-[.8rem] text-ts-muted">Pick a day, then a time that suits you.</p>
  <div class="mb-5 flex gap-2 overflow-x-auto px-0.5 pt-0.5 pb-1.5" role="radiogroup" aria-label="Choose a day">
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-14" aria-label="Monday 14 September"><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Mon</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">14</b></label>
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-15" aria-label="Tuesday 15 September" checked><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Tue</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">15</b></label>
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-16" aria-label="Wednesday 16 September"><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Wed</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">16</b></label>
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-17" aria-label="Thursday 17 September"><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Thu</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">17</b></label>
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-18" aria-label="Friday 18 September"><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Fri</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">18</b></label>
    <label class="group relative flex-none basis-[62px] cursor-pointer rounded-[11px] border border-ts-line py-[9px] text-center transition-[background-color,border-color] duration-[140ms] hover:border-[#c2cbd8] has-checked:border-ts-accent has-checked:bg-ts-accent has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="day" value="2026-09-21" aria-label="Monday 21 September"><small class="block text-[.64rem] font-extrabold tracking-[.06em] text-ts-muted uppercase group-has-checked:text-[#e9e1fb]">Mon</small><b class="mt-0.5 block text-[1.05rem] group-has-checked:text-white">21</b></label>
  </div>
  <div class="grid grid-cols-[repeat(auto-fill,minmax(94px,1fr))] gap-[9px] p-0.5" role="radiogroup" aria-label="Choose a time">
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="09:00" aria-label="Tuesday 15 September, 09:00">09:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="09:30" aria-label="Tuesday 15 September, 09:30, taken" disabled>09:30</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="10:00" aria-label="Tuesday 15 September, 10:00">10:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="10:30" aria-label="Tuesday 15 September, 10:30" checked>10:30</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="11:00" aria-label="Tuesday 15 September, 11:00, taken" disabled>11:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="13:00" aria-label="Tuesday 15 September, 13:00">13:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="13:30" aria-label="Tuesday 15 September, 13:30">13:30</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="14:00" aria-label="Tuesday 15 September, 14:00">14:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="15:00" aria-label="Tuesday 15 September, 15:00, taken" disabled>15:00</label>
    <label class="relative cursor-pointer rounded-[10px] border border-ts-line py-2.5 text-center text-[.85rem] font-bold transition-[background-color,border-color] duration-[140ms] hover:border-ts-accent hover:text-ts-accent has-checked:border-ts-accent has-checked:bg-ts-accent has-checked:text-white has-disabled:cursor-not-allowed has-disabled:border-ts-line has-disabled:bg-[#f6f8fa] has-disabled:text-[#667284] has-disabled:line-through has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ts-accent motion-reduce:transition-none"><input type="radio" class="sr-only" name="time" value="16:00" aria-label="Tuesday 15 September, 16:00">16:00</label>
  </div>
  <p class="mt-[18px] flex items-center gap-[7px] text-[.78rem] text-ts-muted"><svg class="block size-3.5 fill-none stroke-current stroke-2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>Times shown in Europe/London (BST)</p>
</div>
CSS
@theme {
  --color-ts-accent: #6d28d9;
  --color-ts-line: #dbe1ea;
  --color-ts-muted: #5f6b7c;
}
JavaScript
/* Each time slot's accessible name carries the chosen day ("Tuesday 15 September, 10:30"),
   so a screen reader user hears the full appointment. Re-run when the day changes. */
document.querySelectorAll('.tslot').forEach(root => {
  const name = () => {
    const day = root.querySelector('[name=day]:checked').getAttribute('aria-label');
    root.querySelectorAll('[name=time]').forEach(t =>
      t.setAttribute('aria-label', day + ', ' + t.value + (t.disabled ? ', taken' : '')));
  };
  root.addEventListener('change', e => { if (e.target.name === 'day') name(); });
  name();
});

About this set

A booking card for a 30-minute call: a horizontal strip of six days, a grid of ten time slots and a time zone note. Three slots are taken and cannot be picked; they are disabled, struck through and greyed, so the state does not rely on colour. Use it on a contact page, a consultation form or any flow where a visitor books one slot. Both rows are groups of real radio inputs, visually hidden, with the label after each input drawn as the button, so the browser handles selection, arrow keys and form submission. Each day input is named in full, for example Tuesday 15 September, and a short script copies the chosen day into every slot name, so a screen reader hears Tuesday 15 September, 10:30 rather than a bare time. The slot grid uses auto-fill columns, so it reflows from four columns on a desktop to two on a phone, and the day strip scrolls sideways when it runs out of room. The Bootstrap version uses .btn-check with .btn labels, and the Tailwind version wraps each input in its label and styles it with has-checked utilities.

What’s included

  • Day strip and time slot grid built from native radio inputs
  • Taken slots are disabled and struck through, not only greyed
  • Slot names include the day, e.g. "Tuesday 15 September, 10:30"
  • Slot grid reflows with auto-fill columns; day strip scrolls on small screens
  • Plain CSS, Bootstrap .btn-check and Tailwind versions of the same design

Accessibility

Each row is a radiogroup with an aria-label, and every input has a full accessible name; taken slots are disabled and their names end in "taken". Tab moves between the two groups and the arrow keys change the selection. Keyboard focus draws a 2px violet outline around the visible button, and hover and selection transitions are removed under prefers-reduced-motion.

Customise it

Change --ts-accent, --ts-line and --ts-muted on .tslot in plain CSS and Bootstrap (Bootstrap maps them onto the --bs-btn-* variables). In Tailwind edit the --color-ts-* theme colours; slot width is the 94px in the grid template.