Tailwind Calendar: 16 Date Picker and Calendar Examples

Tailwind CSS does not include a calendar. It only styles one, so the dates, month switching and selection come from your own JavaScript or a library such as React DayPicker.

Some people want a date picker for a form field, others a scheduler with appointments across weeks. Below are 16 Tailwind calendar examples you can copy, followed by a guide to kits, libraries and building your own.

By Bogdan Sandu · Updated October 29, 2026

Every example is built with Tailwind CSS v4.3 utility classes. To try one quickly, add the Play CDN tags below to your page and paste the example's HTML, CSS and JS. The dark variant line makes dark: classes follow a .dark class instead of the system setting. For production, install Tailwind with Vite or the CLI.

<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
  @custom-variant dark (&:where(.dark, .dark *));
</style>

Date pickers

Pickers that follow the W3C APG date picker pattern: a role="grid" month with a roving tabindex, arrow keys, Page Up and Page Down, aria-selected for the choice and aria-current="date" for today, shown as a modal dialog, an inline calendar, a popover and a booking range. The last one keeps the native date input and styles its pseudo-elements.

APG date picker dialog

  • <dialog>
  • role="grid"
  • aria-selected:
  • aria-[current=date]:
  • data-outside:
  • backdrop:

Click the calendar button, then use the arrow keys, Page Up, Page Down, Enter and Escape.

The W3C APG date picker dialog pattern on a native dialog opened with showModal(), so Escape, the backdrop:bg-slate-900/40 overlay and the focus return are handled by the browser. The month is a role="grid" of gridcells with a roving tabindex: arrow keys move by day and week, Home and End jump to the week edges, Page Up and Page Down change the month (with Shift, the year), and Enter picks the date. Tailwind styles every state from attributes: aria-selected: for the chosen date, aria-[current=date]: for today and data-outside: for the neighboring months.

View code
<div class="w-full max-w-sm rounded-2xl bg-white p-5 shadow-sm ring-1 ring-slate-900/5">
  <label for="tcl-dp-input" class="block text-sm font-semibold text-slate-900">Delivery date</label>
  <p id="tcl-dp-help" class="text-sm text-slate-600">Format: mm/dd/yyyy</p>
  <div class="mt-2 flex gap-2">
    <input id="tcl-dp-input" type="text" inputmode="numeric" autocomplete="off" aria-describedby="tcl-dp-help" value="06/15/2027" class="min-h-11 w-full min-w-0 rounded-lg border border-slate-300 bg-white px-3 text-slate-900 tabular-nums focus-visible:border-teal-700 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-teal-700">
    <button type="button" id="tcl-dp-open" aria-label="Change date, Tuesday, June 15, 2027" class="grid size-11 shrink-0 place-items-center rounded-lg bg-teal-700 text-white hover:bg-teal-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">
      <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><rect x="3.5" y="5" width="17" height="15.5" rx="2"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>
    </button>
  </div>
  <p class="mt-3 text-sm text-slate-600">We deliver Monday to Saturday between 8 AM and 6 PM.</p>
  <dialog id="tcl-dp-dialog" aria-modal="true" aria-labelledby="tcl-dp-title" class="m-auto w-[min(22rem,calc(100vw-2rem))] rounded-2xl bg-white p-4 text-slate-900 shadow-xl backdrop:bg-slate-900/40">
    <div class="flex items-center gap-1">
      <button type="button" class="tcl-dp-nav grid size-9 place-items-center rounded-lg text-slate-700 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-teal-700" data-step="-12" aria-label="Previous year"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m10 5-5 5 5 5M15 5l-5 5 5 5"/></svg></button>
      <button type="button" class="tcl-dp-nav grid size-9 place-items-center rounded-lg text-slate-700 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-teal-700" data-step="-1" aria-label="Previous month"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
      <h2 id="tcl-dp-title" aria-live="polite" class="flex-1 text-center text-base font-semibold">June 2027</h2>
      <button type="button" class="tcl-dp-nav grid size-9 place-items-center rounded-lg text-slate-700 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-teal-700" data-step="1" aria-label="Next month"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
      <button type="button" class="tcl-dp-nav grid size-9 place-items-center rounded-lg text-slate-700 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-teal-700" data-step="12" aria-label="Next year"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m10 5 5 5-5 5M5 5l5 5-5 5"/></svg></button>
    </div>
    <div id="tcl-dp-grid" role="grid" aria-labelledby="tcl-dp-title" class="mt-3 grid gap-1"></div>
    <div class="mt-4 flex justify-end gap-2 border-t border-slate-100 pt-3">
      <button type="button" id="tcl-dp-cancel" class="min-h-10 rounded-lg px-4 text-sm font-semibold text-slate-700 hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-teal-700">Cancel</button>
      <button type="button" id="tcl-dp-ok" class="min-h-10 rounded-lg bg-teal-700 px-4 text-sm font-semibold text-white hover:bg-teal-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">OK</button>
    </div>
  </dialog>
</div>

Inline scheduling calendar with disabled days

  • aria-disabled:
  • aria-selected:
  • tabindex roving
  • md:grid-cols-[1fr_15rem]
  • disabled:

Click a weekday, or focus the grid and use the arrow keys and Enter.

An always-visible picker for scheduling a post: weekends and past days carry aria-disabled="true", so they stay reachable by arrow keys (screen reader users still hear them) but cannot be picked, and aria-disabled: greys and strikes them out. The previous-month button is a real disabled button styled with disabled:opacity-40. The side panel stacks under the grid on phones and moves beside it with md:grid-cols-[1fr_15rem].

View code
<div class="grid w-full max-w-2xl overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-violet-950/10 md:grid-cols-[1fr_15rem]">
  <div class="p-5">
    <div class="flex items-center gap-2">
      <h2 id="tcl-ip-title" aria-live="polite" class="mr-auto text-base font-semibold text-gray-900">June 2027</h2>
      <button type="button" id="tcl-ip-prev" aria-label="Previous month" class="grid size-10 place-items-center rounded-lg text-gray-700 ring-1 ring-gray-200 hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
      <button type="button" id="tcl-ip-next" aria-label="Next month" class="grid size-10 place-items-center rounded-lg text-gray-700 ring-1 ring-gray-200 hover:bg-gray-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
    </div>
    <div id="tcl-ip-grid" role="grid" aria-labelledby="tcl-ip-title" class="mt-4 grid gap-1"></div>
  </div>
  <div class="flex flex-col gap-4 border-t border-gray-100 bg-violet-50/60 p-5 md:border-t-0 md:border-l">
    <div>
      <p class="text-sm font-semibold text-gray-900">Publish on</p>
      <p id="tcl-ip-out" aria-live="polite" class="mt-1 text-lg font-semibold text-violet-800">Friday, June 18, 2027</p>
    </div>
    <div>
      <label for="tcl-ip-time" class="text-sm font-semibold text-gray-900">Time</label>
      <div class="mt-1.5 grid">
        <select id="tcl-ip-time" class="col-start-1 row-start-1 min-h-11 w-full appearance-none rounded-lg border border-gray-300 bg-white pr-9 pl-3 text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-violet-700">
          <option>8:00 AM</option>
          <option selected>9:30 AM</option>
          <option>12:00 PM</option>
          <option>5:00 PM</option>
        </select>
        <svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="pointer-events-none col-start-1 row-start-1 mr-3 size-4 self-center justify-self-end text-gray-600"><path d="m6 8 4 4 4-4"/></svg>
      </div>
    </div>
    <p class="text-sm text-gray-700">Posts go out on weekdays only. Past dates are locked.</p>
    <button type="button" class="mt-auto min-h-11 rounded-lg bg-violet-700 px-4 text-sm font-semibold text-white hover:bg-violet-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-700">Schedule post</button>
  </div>
</div>

Due date popover with quick picks

  • popover
  • popovertarget
  • position-area
  • role="grid"
  • md:grid-cols-[9rem_1fr]

Click the due date chip, then pick a quick option or a date.

The calendar lives in a popover="auto" element, so the browser handles light dismiss, Escape and the top layer, and the trigger needs no aria-expanded bookkeeping. Opening it moves focus to the selected date in the role="grid"; picking a date calls hidePopover() and hands focus back to the trigger. Quick picks sit in a column at md: and in a two-by-two grid on phones, and a short @supports block anchors the panel under the chip with position-area.

View code
<div class="w-full max-w-md self-start rounded-2xl bg-white p-5 shadow-sm ring-1 ring-stone-900/5">
  <div class="flex items-start gap-3">
    <span aria-hidden="true" class="mt-0.5 size-5 shrink-0 rounded-full border-2 border-stone-400"></span>
    <div class="min-w-0">
      <h2 class="text-base font-semibold text-stone-900">Rewrite the pricing page intro</h2>
      <p class="mt-1 text-sm text-stone-600">Marketing site, assigned to you</p>
    </div>
  </div>
  <div class="mt-4 flex flex-wrap items-center gap-2 border-t border-stone-100 pt-4">
    <span class="text-sm text-stone-600">Due</span>
    <button type="button" id="tcl-pp-btn" popovertarget="tcl-pp-pop" class="inline-flex min-h-10 items-center gap-2 rounded-full bg-rose-50 px-3 text-sm font-semibold text-rose-800 ring-1 ring-rose-200 [anchor-name:--tcl-pp] hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-700">
      <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="size-4"><rect x="3.5" y="5" width="17" height="15.5" rx="2"/><path d="M3.5 10h17M8 3v4M16 3v4"/></svg>
      <span id="tcl-pp-label">Fri, Jun 18</span>
    </button>
    <span class="rounded-full bg-stone-100 px-2.5 py-1 text-xs font-semibold text-stone-700">High priority</span>
  </div>
  <div id="tcl-pp-pop" popover role="dialog" aria-label="Choose a due date" class="tcl-pp-pos m-auto w-[min(31rem,calc(100vw-1.5rem))] rounded-2xl bg-white p-3 text-stone-900 shadow-xl ring-1 ring-stone-900/10 md:grid md:grid-cols-[9rem_1fr] md:gap-3">
    <ul class="grid grid-cols-2 gap-1 border-b border-stone-100 pb-3 md:grid-cols-1 md:content-start md:border-r md:border-b-0 md:pr-3 md:pb-0" aria-label="Quick picks">
      <li><button type="button" class="tcl-pp-quick flex min-h-10 w-full items-center justify-between rounded-lg px-2.5 text-left text-sm hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700" data-date="2027-06-15">Today <span class="text-xs text-stone-600">Tue</span></button></li>
      <li><button type="button" class="tcl-pp-quick flex min-h-10 w-full items-center justify-between rounded-lg px-2.5 text-left text-sm hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700" data-date="2027-06-16">Tomorrow <span class="text-xs text-stone-600">Wed</span></button></li>
      <li><button type="button" class="tcl-pp-quick flex min-h-10 w-full items-center justify-between rounded-lg px-2.5 text-left text-sm hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700" data-date="2027-06-21">Next week <span class="text-xs text-stone-600">Mon</span></button></li>
      <li><button type="button" class="tcl-pp-quick flex min-h-10 w-full items-center justify-between rounded-lg px-2.5 text-left text-sm hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700" data-date="2027-06-29">In 2 weeks <span class="text-xs text-stone-600">Tue</span></button></li>
    </ul>
    <div class="pt-3 md:pt-0">
      <div class="flex items-center">
        <button type="button" id="tcl-pp-prev" aria-label="Previous month" class="grid size-9 place-items-center rounded-lg text-stone-700 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
        <h2 id="tcl-pp-title" aria-live="polite" class="flex-1 text-center text-sm font-semibold">June 2027</h2>
        <button type="button" id="tcl-pp-next" aria-label="Next month" class="grid size-9 place-items-center rounded-lg text-stone-700 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-rose-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
      </div>
      <div id="tcl-pp-grid" role="grid" aria-labelledby="tcl-pp-title" class="mt-2 grid gap-0.5"></div>
    </div>
  </div>
</div>

Booking range picker with hover preview

  • data-in-range:
  • data-[band=start]:
  • group-data-start:
  • md:grid-cols-2
  • matchMedia

Click a check-in date, move to a later date to preview, click again to confirm.

A two-month range picker for a cabin booking (one month on phones, chosen with matchMedia so keyboard focus never lands in a hidden month). The first click sets check-in; moving the pointer or keyboard focus then paints a preview, because the script only flips data-in-range and data-band attributes and Tailwind does the rest: data-in-range: fills the band, data-[band=start]: and data-[band=end]: round its ends, and group-data-start: fills the circles. Booked nights are aria-disabled, and a range that would cross one starts over, like DayPicker with excludeDisabled.

View code
<div class="w-full max-w-3xl rounded-2xl bg-white p-5 shadow-sm ring-1 ring-sky-950/10">
  <div class="flex flex-wrap items-end justify-between gap-3">
    <div>
      <h2 class="text-base font-semibold text-gray-900">Driftwood Cabin, Halsvik</h2>
      <p class="text-sm text-gray-600">$185 per night, cleaning and linens included</p>
    </div>
    <button type="button" id="tcl-rp-clear" class="min-h-10 rounded-lg px-3 text-sm font-semibold text-sky-800 hover:bg-sky-50 focus-visible:outline-2 focus-visible:outline-sky-700">Clear dates</button>
  </div>
  <div class="relative mt-5">
    <button type="button" id="tcl-rp-prev" aria-label="Previous month" class="absolute top-0 left-0 grid size-9 place-items-center rounded-lg text-gray-700 hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-sky-700 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
    <button type="button" id="tcl-rp-next" aria-label="Next month" class="absolute top-0 right-0 grid size-9 place-items-center rounded-lg text-gray-700 hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-sky-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
    <div id="tcl-rp-months" class="grid gap-8 md:grid-cols-2"></div>
  </div>
  <div class="mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 pt-4">
    <p id="tcl-rp-summary" aria-live="polite" class="text-sm text-gray-700"></p>
    <button type="button" class="min-h-11 rounded-lg bg-sky-800 px-5 text-sm font-semibold text-white hover:bg-sky-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-800">Reserve</button>
  </div>
</div>

Native date and time inputs, styled

  • type="date"
  • [&::-webkit-calendar-picker-indicator]:
  • user-invalid:
  • scheme-light
  • min / max

Change the dates. A check-out before check-in turns the field red.

Plain input type="date" and type="time" fields keep the browser picker, the keyboard handling and the mobile wheel UI for free. Arbitrary variants reach the Chromium and Safari pseudo-elements: [&::-webkit-calendar-picker-indicator]: pads and tints the calendar icon, and [&::-webkit-datetime-edit-day-field:focus]: colors the focused segment (Firefox ignores them and keeps its own look). min and max limit the picker, the script moves the check-out min and sets a custom validity message, and user-invalid: only shows the red border after the user has touched the field.

View code
<form id="tcl-nd" class="w-full max-w-md rounded-2xl bg-white p-5 shadow-sm ring-1 ring-stone-900/5">
  <h2 class="text-base font-semibold text-stone-900">Request a stay</h2>
  <p class="text-sm text-stone-600">Juniper Lodge, open June to December 2027</p>
  <div class="mt-4 grid gap-4 sm:grid-cols-2">
    <div>
      <label for="tcl-nd-in" class="text-sm font-semibold text-stone-900">Check-in</label>
      <input id="tcl-nd-in" type="date" required min="2027-06-11" max="2027-12-30" value="2027-06-14" class="mt-1.5 min-h-11 w-full min-w-0 rounded-lg border border-stone-300 bg-white px-3 text-stone-900 scheme-light focus-visible:border-amber-700 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-amber-700 user-invalid:border-red-700 [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:rounded-md [&::-webkit-calendar-picker-indicator]:p-1 [&::-webkit-calendar-picker-indicator]:opacity-70 [&::-webkit-calendar-picker-indicator:hover]:bg-amber-100 [&::-webkit-calendar-picker-indicator:hover]:opacity-100 [&::-webkit-datetime-edit-day-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-day-field:focus]:text-stone-950 [&::-webkit-datetime-edit-month-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-month-field:focus]:text-stone-950 [&::-webkit-datetime-edit-year-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-year-field:focus]:text-stone-950">
    </div>
    <div>
      <label for="tcl-nd-out" class="text-sm font-semibold text-stone-900">Check-out</label>
      <input id="tcl-nd-out" type="date" required min="2027-06-15" max="2027-12-31" value="2027-06-18" aria-describedby="tcl-nd-err" class="mt-1.5 min-h-11 w-full min-w-0 rounded-lg border border-stone-300 bg-white px-3 text-stone-900 scheme-light focus-visible:border-amber-700 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-amber-700 user-invalid:border-red-700 [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:rounded-md [&::-webkit-calendar-picker-indicator]:p-1 [&::-webkit-calendar-picker-indicator]:opacity-70 [&::-webkit-calendar-picker-indicator:hover]:bg-amber-100 [&::-webkit-calendar-picker-indicator:hover]:opacity-100 [&::-webkit-datetime-edit-day-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-day-field:focus]:text-stone-950 [&::-webkit-datetime-edit-month-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-month-field:focus]:text-stone-950 [&::-webkit-datetime-edit-year-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-year-field:focus]:text-stone-950">
    </div>
    <div class="sm:col-span-2">
      <label for="tcl-nd-time" class="text-sm font-semibold text-stone-900">Arrival time</label>
      <input id="tcl-nd-time" type="time" min="15:00" max="22:00" step="900" value="16:00" aria-describedby="tcl-nd-time-help" class="mt-1.5 min-h-11 w-full min-w-0 rounded-lg border border-stone-300 bg-white px-3 text-stone-900 scheme-light focus-visible:border-amber-700 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-amber-700 user-invalid:border-red-700 [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:rounded-md [&::-webkit-calendar-picker-indicator]:p-1 [&::-webkit-calendar-picker-indicator]:opacity-70 [&::-webkit-calendar-picker-indicator:hover]:bg-amber-100 [&::-webkit-calendar-picker-indicator:hover]:opacity-100 [&::-webkit-datetime-edit-hour-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-hour-field:focus]:text-stone-950 [&::-webkit-datetime-edit-minute-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-minute-field:focus]:text-stone-950 [&::-webkit-datetime-edit-ampm-field:focus]:bg-amber-200 [&::-webkit-datetime-edit-ampm-field:focus]:text-stone-950">
      <p id="tcl-nd-time-help" class="mt-1 text-sm text-stone-600">Reception is open from 3 PM to 10 PM.</p>
    </div>
  </div>
  <p id="tcl-nd-err" class="mt-3 text-sm font-medium text-red-700"></p>
  <div class="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-stone-100 pt-4">
    <p class="text-sm text-stone-700"><output id="tcl-nd-nights" for="tcl-nd-in tcl-nd-out" class="font-semibold text-stone-900">4 nights</output> selected</p>
    <button type="submit" class="min-h-11 rounded-lg bg-amber-700 px-5 text-sm font-semibold text-white hover:bg-amber-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-700">Send request</button>
  </div>
  <p id="tcl-nd-status" role="status" class="mt-3 text-sm text-stone-700 empty:hidden"></p>
</form>

Event calendars: month, week, day and year

Scheduler layouts built only with Tailwind grid utilities: a month board with event pills and an overflow popover, a week view placed on CSS grid rows, a day agenda for phones and a year heatmap.

Event calendar month view with "+2 more"

  • grid-cols-7
  • gap-px
  • md:min-h-28
  • popovertarget
  • aria-pressed:
  • max-lg:sr-only

Click "+2 more" on June 8 or 15. On a phone, tap a day to list its events.

The classic month view: a grid-cols-7 board whose gap-px over bg-zinc-200 draws the hairlines. Each day shows two event pills and a "+2 more" button that opens a popover with the full list, anchored with position-area where supported. On phones the pills are hidden (hidden md:block), each day shows colored dots instead, and tapping a day fills the list under the grid, which is how most calendar apps handle seven columns at 320px.

View code
<div id="tcl-me" class="w-full max-w-4xl overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-zinc-900/5">
  <header class="flex flex-wrap items-center justify-between gap-3 border-b border-zinc-200 px-4 py-3">
    <h2 class="text-base font-semibold text-zinc-900"><time datetime="2027-06">June 2027</time></h2>
    <div class="flex items-center gap-2">
      <div class="flex rounded-lg ring-1 ring-zinc-300">
        <button type="button" aria-label="Previous month" class="grid size-10 place-items-center rounded-l-lg text-zinc-700 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-orange-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
        <button type="button" class="border-x border-zinc-300 px-3 text-sm font-semibold text-zinc-900 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-orange-700">Today</button>
        <button type="button" aria-label="Next month" class="grid size-10 place-items-center rounded-r-lg text-zinc-700 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-orange-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
      </div>
      <button type="button" class="min-h-10 rounded-lg bg-zinc-900 px-3 text-sm font-semibold text-white hover:bg-zinc-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-900">Add event</button>
    </div>
  </header>
  <div aria-hidden="true" class="grid grid-cols-7 border-b border-zinc-200 text-center text-xs font-semibold text-zinc-600"><div class="py-2">S<span class="sr-only md:not-sr-only">un</span></div><div class="py-2">M<span class="sr-only md:not-sr-only">on</span></div><div class="py-2">T<span class="sr-only md:not-sr-only">ue</span></div><div class="py-2">W<span class="sr-only md:not-sr-only">ed</span></div><div class="py-2">T<span class="sr-only md:not-sr-only">hu</span></div><div class="py-2">F<span class="sr-only md:not-sr-only">ri</span></div><div class="py-2">S<span class="sr-only md:not-sr-only">at</span></div></div>
  <div class="grid grid-cols-7 gap-px bg-zinc-200">
    <div class="tcl-me-cell flex min-h-14 flex-col bg-zinc-50 px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-05-30">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs text-zinc-600" aria-pressed="false" aria-label="Sunday, May 30, no events"><time datetime="2027-05-30">30</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-zinc-50 px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-05-31">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs text-zinc-600" aria-pressed="false" aria-label="Monday, May 31, no events"><time datetime="2027-05-31">31</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-01">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Tuesday, June 1, 1 event"><time datetime="2027-06-01">1</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Sprint planning" data-time="9:00 AM"><span class="truncate font-medium">Sprint planning</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">9:00 AM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-sky-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-02">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Wednesday, June 2, no events"><time datetime="2027-06-02">2</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-03">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Thursday, June 3, 1 event"><time datetime="2027-06-03">3</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Design review" data-time="2:00 PM"><span class="truncate font-medium">Design review</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">2:00 PM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-violet-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-04">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Friday, June 4, 1 event"><time datetime="2027-06-04">4</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-amber-50 text-amber-900 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Team dinner" data-time="6:30 PM"><span class="truncate font-medium">Team dinner</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">6:30 PM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-amber-500"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-05">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Saturday, June 5, no events"><time datetime="2027-06-05">5</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-06">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Sunday, June 6, no events"><time datetime="2027-06-06">6</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-07">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Monday, June 7, no events"><time datetime="2027-06-07">7</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-08">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Tuesday, June 8, 4 events"><time datetime="2027-06-08">8</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Client call: Harbor Co" data-time="10:00 AM"><span class="truncate font-medium">Client call: Harbor Co</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">10:00 AM</span></a></li>
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Content audit" data-time="1:00 PM"><span class="truncate font-medium">Content audit</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">1:00 PM</span></a></li>
        <li class="hidden"><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Hiring sync" data-time="4:00 PM"><span class="truncate font-medium">Hiring sync</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">4:00 PM</span></a></li>
        <li class="hidden"><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-rose-50 text-rose-900 hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Yoga" data-time="5:30 PM"><span class="truncate font-medium">Yoga</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">5:30 PM</span></a></li>
        <li><button type="button" popovertarget="tcl-me-pop-8" class="min-h-6 rounded-md px-1.5 text-xs font-semibold text-zinc-700 [anchor-name:--tcl-me-8] hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-orange-700">+2 more</button></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-emerald-600"></span><span class="size-1.5 rounded-full bg-sky-600"></span><span class="size-1.5 rounded-full bg-violet-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-09">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Wednesday, June 9, no events"><time datetime="2027-06-09">9</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-10">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Thursday, June 10, no events"><time datetime="2027-06-10">10</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-11">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Friday, June 11, 1 event"><time datetime="2027-06-11">11</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-rose-50 text-rose-900 hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Release 4.2" data-time="All day"><span class="truncate font-medium">Release 4.2</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">All day</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-rose-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-12">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Saturday, June 12, no events"><time datetime="2027-06-12">12</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-13">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Sunday, June 13, no events"><time datetime="2027-06-13">13</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-14">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Monday, June 14, no events"><time datetime="2027-06-14">14</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-15">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-current="date" aria-label="Tuesday, June 15, 4 events"><time datetime="2027-06-15">15</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Standup" data-time="9:30 AM"><span class="truncate font-medium">Standup</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">9:30 AM</span></a></li>
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-amber-50 text-amber-900 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Brand workshop" data-time="11:00 AM"><span class="truncate font-medium">Brand workshop</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">11:00 AM</span></a></li>
        <li class="hidden"><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="1:1 with Priya" data-time="3:00 PM"><span class="truncate font-medium">1:1 with Priya</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">3:00 PM</span></a></li>
        <li class="hidden"><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Book club" data-time="7:00 PM"><span class="truncate font-medium">Book club</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">7:00 PM</span></a></li>
        <li><button type="button" popovertarget="tcl-me-pop-15" class="min-h-6 rounded-md px-1.5 text-xs font-semibold text-zinc-700 [anchor-name:--tcl-me-15] hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-orange-700">+2 more</button></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-emerald-600"></span><span class="size-1.5 rounded-full bg-amber-500"></span><span class="size-1.5 rounded-full bg-sky-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-16">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Wednesday, June 16, no events"><time datetime="2027-06-16">16</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-17">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Thursday, June 17, 1 event"><time datetime="2027-06-17">17</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-amber-50 text-amber-900 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Lunch and learn" data-time="12:00 PM"><span class="truncate font-medium">Lunch and learn</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">12:00 PM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-amber-500"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-18">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Friday, June 18, no events"><time datetime="2027-06-18">18</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-19">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Saturday, June 19, no events"><time datetime="2027-06-19">19</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-20">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Sunday, June 20, no events"><time datetime="2027-06-20">20</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-21">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Monday, June 21, 1 event"><time datetime="2027-06-21">21</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Team offsite" data-time="All day"><span class="truncate font-medium">Team offsite</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">All day</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-emerald-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-22">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Tuesday, June 22, 2 events"><time datetime="2027-06-22">22</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Team offsite" data-time="All day"><span class="truncate font-medium">Team offsite</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">All day</span></a></li>
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-amber-50 text-amber-900 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Offsite dinner" data-time="8:00 PM"><span class="truncate font-medium">Offsite dinner</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">8:00 PM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-emerald-600"></span><span class="size-1.5 rounded-full bg-amber-500"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-23">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Wednesday, June 23, no events"><time datetime="2027-06-23">23</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-24">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Thursday, June 24, 1 event"><time datetime="2027-06-24">24</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Roadmap review" data-time="10:00 AM"><span class="truncate font-medium">Roadmap review</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">10:00 AM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-violet-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-25">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Friday, June 25, no events"><time datetime="2027-06-25">25</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-26">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Saturday, June 26, no events"><time datetime="2027-06-26">26</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-27">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Sunday, June 27, no events"><time datetime="2027-06-27">27</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-28">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Monday, June 28, no events"><time datetime="2027-06-28">28</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-29">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Tuesday, June 29, 1 event"><time datetime="2027-06-29">29</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-rose-50 text-rose-900 hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Quarterly report due" data-time="3:00 PM"><span class="truncate font-medium">Quarterly report due</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">3:00 PM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-rose-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-white px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-06-30">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs" aria-pressed="false" aria-label="Wednesday, June 30, 1 event"><time datetime="2027-06-30">30</time></button>
      <ol class="mt-1 hidden space-y-1 md:block">
        <li><a href="#" class="tcl-me-event flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Board prep" data-time="9:00 AM"><span class="truncate font-medium">Board prep</span><span class="ml-auto shrink-0 text-[11px] opacity-80 max-lg:sr-only">9:00 AM</span></a></li>
      </ol>
      <span aria-hidden="true" class="mt-0.5 flex h-1.5 justify-center gap-0.5 md:hidden"><span class="size-1.5 rounded-full bg-sky-600"></span></span>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-zinc-50 px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-07-01">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs text-zinc-600" aria-pressed="false" aria-label="Thursday, July 1, no events"><time datetime="2027-07-01">1</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-zinc-50 px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-07-02">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs text-zinc-600" aria-pressed="false" aria-label="Friday, July 2, no events"><time datetime="2027-07-02">2</time></button>
    </div>
    <div class="tcl-me-cell flex min-h-14 flex-col bg-zinc-50 px-0.5 py-1.5 md:min-h-28 md:p-1.5" data-date="2027-07-03">
      <button type="button" class="tcl-me-day mx-auto grid size-9 place-items-center rounded-full text-sm text-zinc-900 tabular-nums hover:bg-zinc-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-orange-700 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 aria-pressed:bg-orange-700 aria-pressed:font-semibold aria-pressed:text-white md:mx-0 md:size-7 md:text-xs text-zinc-600" aria-pressed="false" aria-label="Saturday, July 3, no events"><time datetime="2027-07-03">3</time></button>
    </div>
  </div>
  <section class="border-t border-zinc-200 p-4 md:hidden">
    <h3 id="tcl-me-day-title" aria-live="polite" class="text-sm font-semibold text-zinc-900">Tuesday, June 15</h3>
    <ol id="tcl-me-list" class="mt-1 divide-y divide-zinc-100"></ol>
  </section>
  <div id="tcl-me-pop-8" popover class="tcl-me-pop m-auto w-64 rounded-xl bg-white p-3 shadow-lg ring-1 ring-zinc-900/10 [position-anchor:--tcl-me-8]">
    <h3 class="text-sm font-semibold text-zinc-900">Tuesday, June 8</h3>
    <ul class="mt-2 space-y-1">
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Client call: Harbor Co" data-time="10:00 AM"><span class="truncate font-medium">Client call: Harbor Co</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">10:00 AM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Content audit" data-time="1:00 PM"><span class="truncate font-medium">Content audit</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">1:00 PM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Hiring sync" data-time="4:00 PM"><span class="truncate font-medium">Hiring sync</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">4:00 PM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-rose-50 text-rose-900 hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Yoga" data-time="5:30 PM"><span class="truncate font-medium">Yoga</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">5:30 PM</span></a></li>
    </ul>
  </div>
  <div id="tcl-me-pop-15" popover class="tcl-me-pop m-auto w-64 rounded-xl bg-white p-3 shadow-lg ring-1 ring-zinc-900/10 [position-anchor:--tcl-me-15]">
    <h3 class="text-sm font-semibold text-zinc-900">Tuesday, June 15</h3>
    <ul class="mt-2 space-y-1">
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Standup" data-time="9:30 AM"><span class="truncate font-medium">Standup</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">9:30 AM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-amber-50 text-amber-900 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Brand workshop" data-time="11:00 AM"><span class="truncate font-medium">Brand workshop</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">11:00 AM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="1:1 with Priya" data-time="3:00 PM"><span class="truncate font-medium">1:1 with Priya</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">3:00 PM</span></a></li>
      <li><a href="#" class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-orange-700" data-title="Book club" data-time="7:00 PM"><span class="truncate font-medium">Book club</span><span class="ml-auto shrink-0 text-[11px] opacity-80 ">7:00 PM</span></a></li>
    </ul>
  </div>
</div>

Week view with time slots on CSS grid rows

  • grid-rows-[repeat(20,1.75rem)]
  • grid-cols-[3.5rem_repeat(7,minmax(0,1fr))]
  • row-start-*
  • row-span-*
  • overflow-x-auto

On a phone, swipe the grid sideways.

No script and no absolute positioning: the body is one grid with a time column plus seven day columns and twenty half-hour rows, so an event from 11:00 to 12:30 on Tuesday is simply col-start-4 row-start-7 row-span-3. Hour lines, weekend tints and day dividers are empty grid items placed in the same cells, and the events sit above them with z-10. On phones the grid keeps a min-w-[44rem] and scrolls inside its own focusable region instead of squeezing seven columns.

View code
<div class="w-full max-w-4xl overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-900/5">
  <header class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 px-4 py-3">
    <h2 class="text-base font-semibold text-slate-900">Week of June 13, 2027</h2>
    <div class="flex rounded-lg bg-slate-100 p-1 text-sm font-semibold">
      <a href="#" class="rounded-md px-3 py-1.5 text-slate-700 hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-indigo-600">Day</a>
      <a href="#" aria-current="page" class="rounded-md bg-white px-3 py-1.5 text-slate-900 shadow-xs focus-visible:outline-2 focus-visible:outline-indigo-600">Week</a>
      <a href="#" class="rounded-md px-3 py-1.5 text-slate-700 hover:text-slate-900 focus-visible:outline-2 focus-visible:outline-indigo-600">Month</a>
    </div>
  </header>
  <div role="region" aria-label="Week of June 13, 2027" tabindex="0" class="overflow-x-auto overscroll-x-contain focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-indigo-600">
    <div class="min-w-[44rem]">
      <div aria-hidden="true" class="grid grid-cols-[3.5rem_repeat(7,minmax(0,1fr))] border-b border-slate-200 text-center text-xs text-slate-600">
        <div></div>
        <div class="py-2">Sun<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">13</span></div>
        <div class="py-2">Mon<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">14</span></div>
        <div class="py-2">Tue<span class="mx-auto mt-0.5 grid size-8 place-items-center rounded-full bg-indigo-600 text-sm font-semibold text-white">15</span></div>
        <div class="py-2">Wed<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">16</span></div>
        <div class="py-2">Thu<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">17</span></div>
        <div class="py-2">Fri<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">18</span></div>
        <div class="py-2">Sat<span class="mt-0.5 block text-sm leading-8 font-semibold text-slate-900">19</span></div>
      </div>
      <div class="grid grid-cols-[3.5rem_repeat(7,minmax(0,1fr))] grid-rows-[repeat(20,1.75rem)] pt-2 pb-1">
        <div aria-hidden="true" class="col-start-2 row-span-full row-start-1 bg-slate-50"></div>
        <div aria-hidden="true" class="col-start-8 row-span-full row-start-1 bg-slate-50"></div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-1 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-1 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">8 AM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-3 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-3 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">9 AM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-5 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-5 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">10 AM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-7 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-7 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">11 AM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-9 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-9 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">12 PM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-11 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-11 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">1 PM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-13 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-13 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">2 PM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-15 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-15 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">3 PM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-17 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-17 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">4 PM</div>
        <div aria-hidden="true" class="col-span-7 col-start-2 row-start-19 border-t border-slate-200"></div>
        <div aria-hidden="true" class="col-start-1 row-start-19 -translate-y-2 pr-2 text-right text-[11px] text-slate-500">5 PM</div>
        <div aria-hidden="true" class="col-start-3 row-span-full row-start-1 border-l border-slate-200"></div>
        <div aria-hidden="true" class="col-start-4 row-span-full row-start-1 border-l border-slate-200"></div>
        <div aria-hidden="true" class="col-start-5 row-span-full row-start-1 border-l border-slate-200"></div>
        <div aria-hidden="true" class="col-start-6 row-span-full row-start-1 border-l border-slate-200"></div>
        <div aria-hidden="true" class="col-start-7 row-span-full row-start-1 border-l border-slate-200"></div>
        <div aria-hidden="true" class="col-start-8 row-span-full row-start-1 border-l border-slate-200"></div>
        <a href="#" class="relative z-10 col-start-3 row-start-3 row-span-2 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-sky-600 bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Monday, </span><span class="block truncate font-semibold">Design critique</span><time datetime="2027-06-14T09:00" class="opacity-80">9:00 AM</time></a>
        <a href="#" class="relative z-10 col-start-3 row-start-11 row-span-3 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-violet-600 bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Monday, </span><span class="block truncate font-semibold">Pricing page sprint</span><time datetime="2027-06-14T13:00" class="opacity-80">1:00 PM</time></a>
        <a href="#" class="relative z-10 col-start-4 row-start-2 row-span-1 m-0.5 flex items-center gap-1 overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-emerald-600 bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Tuesday, </span><span class="truncate font-semibold">Standup</span> <time datetime="2027-06-15T08:30" class="shrink-0 opacity-80">8:30</time></a>
        <a href="#" class="relative z-10 col-start-4 row-start-7 row-span-3 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-amber-500 bg-amber-50 text-amber-950 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Tuesday, </span><span class="block truncate font-semibold">Brand workshop</span><time datetime="2027-06-15T11:00" class="opacity-80">11:00 AM</time></a>
        <a href="#" class="relative z-10 col-start-4 row-start-15 row-span-1 m-0.5 flex items-center gap-1 overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-sky-600 bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Tuesday, </span><span class="truncate font-semibold">1:1 with Priya</span> <time datetime="2027-06-15T15:00" class="shrink-0 opacity-80">3:00</time></a>
        <a href="#" class="relative z-10 col-start-5 row-start-5 row-span-3 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-sky-600 bg-sky-50 text-sky-900 hover:bg-sky-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Wednesday, </span><span class="block truncate font-semibold">User interviews</span><time datetime="2027-06-16T10:00" class="opacity-80">10:00 AM</time></a>
        <a href="#" class="relative z-10 col-start-5 row-start-13 row-span-4 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-stone-500 bg-stone-100 text-stone-900 hover:bg-stone-200 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Wednesday, </span><span class="block truncate font-semibold">Focus: onboarding copy</span><time datetime="2027-06-16T14:00" class="opacity-80">2:00 PM</time></a>
        <a href="#" class="relative z-10 col-start-6 row-start-2 row-span-1 m-0.5 flex items-center gap-1 overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-emerald-600 bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Thursday, </span><span class="truncate font-semibold">Standup</span> <time datetime="2027-06-17T08:30" class="shrink-0 opacity-80">8:30</time></a>
        <a href="#" class="relative z-10 col-start-6 row-start-9 row-span-2 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-amber-500 bg-amber-50 text-amber-950 hover:bg-amber-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Thursday, </span><span class="block truncate font-semibold">Lunch and learn</span><time datetime="2027-06-17T12:00" class="opacity-80">12:00 PM</time></a>
        <a href="#" class="relative z-10 col-start-6 row-start-17 row-span-3 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-rose-600 bg-rose-50 text-rose-900 hover:bg-rose-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Thursday, </span><span class="block truncate font-semibold">Release review</span><time datetime="2027-06-17T16:00" class="opacity-80">4:00 PM</time></a>
        <a href="#" class="relative z-10 col-start-7 row-start-4 row-span-3 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-violet-600 bg-violet-50 text-violet-900 hover:bg-violet-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Friday, </span><span class="block truncate font-semibold">Roadmap review</span><time datetime="2027-06-18T09:30" class="opacity-80">9:30 AM</time></a>
        <a href="#" class="relative z-10 col-start-7 row-start-15 row-span-2 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-emerald-600 bg-emerald-50 text-emerald-900 hover:bg-emerald-100 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Friday, </span><span class="block truncate font-semibold">Team retro</span><time datetime="2027-06-18T15:00" class="opacity-80">3:00 PM</time></a>
        <a href="#" class="relative z-10 col-start-8 row-start-5 row-span-4 m-0.5 block overflow-hidden rounded-md border-l-[3px] px-1.5 py-1 text-xs/4 border-stone-500 bg-stone-100 text-stone-900 hover:bg-stone-200 focus-visible:outline-2 focus-visible:outline-indigo-600"><span class="sr-only">Saturday, </span><span class="block truncate font-semibold">Climbing gym</span><time datetime="2027-06-19T10:00" class="opacity-80">10:00 AM</time></a>
        <div aria-hidden="true" class="pointer-events-none relative z-20 col-start-4 row-start-6 mt-[0.75rem] h-0.5 bg-rose-600"><span class="absolute -top-[3px] -left-1 size-2 rounded-full bg-rose-600"></span></div>
      </div>
    </div>
  </div>
</div>

Day agenda with a week strip

  • aria-pressed:
  • border-l-4
  • -space-x-2
  • ring-2 ring-white
  • Intl.DateTimeFormat

Pick another day in the strip.

An agenda list for one day, the layout that works best on phones: seven toggle buttons with aria-pressed pick the day, and aria-pressed: paints the active one, so there is no separate active class to keep in sync. The script rebuilds the list with createElement from a small data object and formats every time with Intl.DateTimeFormat. Attendees are initials avatars overlapped with -space-x-2 and ring-2 ring-white, with their names in sr-only text.

View code
<div class="w-full max-w-lg rounded-2xl bg-white shadow-sm ring-1 ring-orange-950/10">
  <div class="border-b border-stone-100 p-4">
    <div class="flex items-baseline justify-between gap-3">
      <h2 class="text-base font-semibold text-stone-900">June 2027</h2>
      <p class="text-sm text-stone-600">Week 24</p>
    </div>
    <div role="group" aria-label="Choose a day" class="mt-3 grid grid-cols-7 gap-1">
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-14" aria-label="Monday, June 14"><span class="text-xs">Mon</span><span class="text-base font-semibold tabular-nums">14</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="true" data-date="2027-06-15" aria-label="Tuesday, June 15"><span class="text-xs">Tue</span><span class="text-base font-semibold tabular-nums">15</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-16" aria-label="Wednesday, June 16"><span class="text-xs">Wed</span><span class="text-base font-semibold tabular-nums">16</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-17" aria-label="Thursday, June 17"><span class="text-xs">Thu</span><span class="text-base font-semibold tabular-nums">17</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-18" aria-label="Friday, June 18"><span class="text-xs">Fri</span><span class="text-base font-semibold tabular-nums">18</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-19" aria-label="Saturday, June 19"><span class="text-xs">Sat</span><span class="text-base font-semibold tabular-nums">19</span></button>
      <button type="button" class="tcl-da-day flex min-h-14 flex-col items-center justify-center rounded-xl text-stone-700 hover:bg-orange-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-700 aria-pressed:bg-orange-700 aria-pressed:text-white aria-pressed:hover:bg-orange-700" aria-pressed="false" data-date="2027-06-20" aria-label="Sunday, June 20"><span class="text-xs">Sun</span><span class="text-base font-semibold tabular-nums">20</span></button>
    </div>
  </div>
  <div class="p-4">
    <div class="flex items-baseline justify-between gap-3">
      <h3 id="tcl-da-title" class="text-sm font-semibold text-stone-900">Tuesday, June 15</h3>
      <p id="tcl-da-count" aria-live="polite" class="text-sm text-stone-600"></p>
    </div>
    <ol id="tcl-da-list" aria-labelledby="tcl-da-title" class="mt-3 space-y-3"></ol>
  </div>
</div>

Year heatmap, contribution style

  • grid-rows-7
  • grid-flow-col
  • auto-cols-[0.75rem]
  • title
  • sr-only

Hover or tap a square.

A whole year as 53 week columns: grid-rows-7 grid-flow-col auto-cols-[0.75rem] fills each column top to bottom, Sunday to Saturday, so the script only appends 371 list items in date order. Each square has a title tooltip for mouse users and the same sentence as sr-only text, and a readout line repeats the hovered or tapped day for touch screens. Five fixed teal shades map the distance to a level; on phones the grid scrolls inside its own focusable region.

View code
<div class="w-full min-w-0 max-w-4xl rounded-2xl bg-white p-5 shadow-sm ring-1 ring-slate-900/5">
  <div class="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
    <h2 id="tcl-hm-title" class="text-base font-semibold text-slate-900">Running log, 2027</h2>
    <p id="tcl-hm-total" class="text-sm text-slate-600"></p>
  </div>
  <div role="region" aria-labelledby="tcl-hm-title" tabindex="0" class="relative mt-4 overflow-x-auto pb-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-700">
    <div class="inline-grid grid-cols-[2rem_auto] gap-1">
      <div></div>
      <div id="tcl-hm-months" aria-hidden="true" class="grid auto-cols-[0.75rem] grid-flow-col gap-[3px] text-xs text-slate-600"></div>
      <div aria-hidden="true" class="grid grid-rows-7 gap-[3px] text-[10px]/3 text-slate-600"><span></span><span>Mon</span><span></span><span>Wed</span><span></span><span>Fri</span><span></span></div>
      <ol id="tcl-hm-grid" aria-label="Distance per day" class="grid auto-cols-[0.75rem] grid-flow-col grid-rows-7 gap-[3px]"></ol>
    </div>
  </div>
  <div class="mt-3 flex flex-wrap items-center justify-between gap-3">
    <p id="tcl-hm-readout" class="min-h-5 text-sm text-slate-700">Hover or tap a day for details.</p>
    <div aria-hidden="true" class="flex items-center gap-1 text-xs text-slate-600">Less
      <span class="size-3 rounded-[3px] bg-slate-100 ring-1 ring-slate-900/5 ring-inset"></span>
      <span class="size-3 rounded-[3px] bg-teal-200 ring-1 ring-slate-900/5 ring-inset"></span>
      <span class="size-3 rounded-[3px] bg-teal-400 ring-1 ring-slate-900/5 ring-inset"></span>
      <span class="size-3 rounded-[3px] bg-teal-600 ring-1 ring-slate-900/5 ring-inset"></span>
      <span class="size-3 rounded-[3px] bg-teal-800 ring-1 ring-slate-900/5 ring-inset"></span>
    More</div>
  </div>
</div>

Calendar widgets and booking

Smaller calendars that sit inside other UI: the plain gap-px month grid, a mini calendar in a dashboard sidebar, a time slot picker for appointments, and a live month that reads the real date.

Month grid with gap-px hairlines

  • grid-cols-7
  • gap-px
  • data-outside:
  • aria-[current=date]:
  • group-aria-pressed:
  • disabled:

Click a date. June 24 and 25 are sold out.

The grid most hand-built Tailwind calendars start from, written as plain HTML: grid-cols-7 with gap-px over a bg-gray-200 container gives hairline borders without a single border class, and isolate keeps the focused cell (focus:z-10) above its neighbors. Every state is an attribute: data-outside for neighboring months, aria-current="date" for today, aria-pressed for the selection (group-aria-pressed: fills the circle) and disabled for sold-out days. The few lines of script only move aria-pressed.

View code
<div class="w-full max-w-xs">
  <div class="flex items-center text-gray-900">
    <button type="button" aria-label="Previous month" class="grid size-10 place-items-center rounded-lg text-gray-600 hover:bg-white hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-indigo-600"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
    <h2 class="flex-auto text-center text-sm font-semibold">June 2027</h2>
    <button type="button" aria-label="Next month" class="grid size-10 place-items-center rounded-lg text-gray-600 hover:bg-white hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-indigo-600"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
  </div>
  <div aria-hidden="true" class="mt-4 grid grid-cols-7 text-center text-xs/6 text-gray-600"><div>S</div><div>M</div><div>T</div><div>W</div><div>T</div><div>F</div><div>S</div></div>
  <div class="isolate mt-2 grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm shadow-xs ring-1 ring-gray-200">
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through rounded-tl-lg" aria-pressed="false" aria-label="Sunday, May 30" data-outside><time datetime="2027-05-30" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">30</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Monday, May 31" data-outside><time datetime="2027-05-31" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">31</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Tuesday, June 1"><time datetime="2027-06-01" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">1</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Wednesday, June 2"><time datetime="2027-06-02" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">2</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Thursday, June 3"><time datetime="2027-06-03" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">3</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Friday, June 4"><time datetime="2027-06-04" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">4</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through rounded-tr-lg" aria-pressed="false" aria-label="Saturday, June 5"><time datetime="2027-06-05" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">5</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Sunday, June 6"><time datetime="2027-06-06" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">6</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Monday, June 7"><time datetime="2027-06-07" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">7</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Tuesday, June 8"><time datetime="2027-06-08" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">8</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Wednesday, June 9"><time datetime="2027-06-09" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">9</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Thursday, June 10"><time datetime="2027-06-10" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">10</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Friday, June 11"><time datetime="2027-06-11" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">11</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Saturday, June 12"><time datetime="2027-06-12" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">12</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Sunday, June 13"><time datetime="2027-06-13" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">13</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Monday, June 14"><time datetime="2027-06-14" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">14</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Tuesday, June 15" aria-current="date"><time datetime="2027-06-15" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">15</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Wednesday, June 16"><time datetime="2027-06-16" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">16</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Thursday, June 17"><time datetime="2027-06-17" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">17</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="true" aria-label="Friday, June 18"><time datetime="2027-06-18" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">18</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Saturday, June 19"><time datetime="2027-06-19" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">19</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Sunday, June 20"><time datetime="2027-06-20" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">20</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Monday, June 21"><time datetime="2027-06-21" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">21</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Tuesday, June 22"><time datetime="2027-06-22" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">22</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Wednesday, June 23"><time datetime="2027-06-23" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">23</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Thursday, June 24, sold out" disabled><time datetime="2027-06-24" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">24</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Friday, June 25, sold out" disabled><time datetime="2027-06-25" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">25</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Saturday, June 26"><time datetime="2027-06-26" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">26</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through rounded-bl-lg" aria-pressed="false" aria-label="Sunday, June 27"><time datetime="2027-06-27" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">27</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Monday, June 28"><time datetime="2027-06-28" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">28</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Tuesday, June 29"><time datetime="2027-06-29" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">29</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Wednesday, June 30"><time datetime="2027-06-30" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">30</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Thursday, July 1" data-outside><time datetime="2027-07-01" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">1</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through" aria-pressed="false" aria-label="Friday, July 2" data-outside><time datetime="2027-07-02" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">2</time></button>
    <button type="button" class="tcl-ms-day group bg-white py-1.5 text-gray-900 hover:bg-gray-100 focus:z-10 focus-visible:outline-2 focus-visible:outline-indigo-600 data-outside:bg-gray-50 data-outside:text-gray-500 aria-[current=date]:font-semibold aria-[current=date]:text-indigo-700 aria-pressed:font-semibold aria-pressed:text-white disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 disabled:line-through rounded-br-lg" aria-pressed="false" aria-label="Saturday, July 3" data-outside><time datetime="2027-07-03" class="mx-auto grid size-7 place-items-center rounded-full tabular-nums group-aria-pressed:bg-gray-900">3</time></button>
  </div>
  <p id="tcl-ms-out" aria-live="polite" class="mt-4 text-center text-sm text-gray-700">Selected: Friday, June 18</p>
</div>

Mini calendar in a dashboard sidebar

  • md:w-72
  • role="grid"
  • group-aria-selected:
  • aria-selected:
  • md:flex-row

Click a day with a dot, or use the arrow keys in the mini calendar.

A compact month that steers the rest of a dashboard: picking a day refills the agenda under it. The cells are size-9 gridcells with the same roving tabindex and arrow-key handling as the full picker, and a dot marks days with events; group-aria-selected: turns the dot white on the selected day. The sidebar is full width above the content on phones and a fixed md:w-72 column beside it from 768px.

View code
<div class="flex min-h-[30rem] flex-col bg-stone-100 md:flex-row">
  <aside class="w-full shrink-0 border-b border-stone-200 bg-white p-4 md:w-72 md:border-r md:border-b-0">
    <p class="flex items-center gap-2 text-base font-bold tracking-tight text-stone-900">
      <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="size-7 rounded-lg bg-orange-700 p-1 text-white"><path d="M4 18 12 5l8 13M8 13h8"/></svg>
      Kestrel
    </p>
    <section aria-labelledby="tcl-mc-title" class="mt-5 rounded-xl border border-stone-200 p-3">
      <div class="flex items-center">
        <h2 id="tcl-mc-title" aria-live="polite" class="mr-auto pl-1 text-sm font-semibold text-stone-900">June 2027</h2>
        <button type="button" id="tcl-mc-prev" aria-label="Previous month" class="grid size-8 place-items-center rounded-md text-stone-600 hover:bg-stone-100 hover:text-stone-900 focus-visible:outline-2 focus-visible:outline-orange-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-4"><path d="m12 5-5 5 5 5"/></svg></button>
        <button type="button" id="tcl-mc-next" aria-label="Next month" class="grid size-8 place-items-center rounded-md text-stone-600 hover:bg-stone-100 hover:text-stone-900 focus-visible:outline-2 focus-visible:outline-orange-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-4"><path d="m8 5 5 5-5 5"/></svg></button>
      </div>
      <div id="tcl-mc-grid" role="grid" aria-labelledby="tcl-mc-title" class="mt-2 grid gap-0.5"></div>
    </section>
    <section class="mt-5">
      <h3 id="tcl-mc-day" aria-live="polite" class="text-sm font-semibold text-stone-900">Tuesday, June 15</h3>
      <ol id="tcl-mc-list" aria-labelledby="tcl-mc-day" class="mt-2 space-y-2"></ol>
    </section>
  </aside>
  <div class="flex-1 p-4 md:p-6">
    <h2 class="text-lg font-semibold text-stone-900">Studio overview</h2>
    <p class="text-sm text-stone-600">Kestrel Design, summer schedule</p>
    <dl class="mt-4 grid gap-3 sm:grid-cols-3">
      <div class="rounded-xl bg-white p-4 ring-1 ring-stone-900/5"><dt class="text-sm text-stone-600">Meetings this week</dt><dd class="mt-1 text-2xl font-semibold text-stone-900">14</dd></div>
      <div class="rounded-xl bg-white p-4 ring-1 ring-stone-900/5"><dt class="text-sm text-stone-600">Focus hours</dt><dd class="mt-1 text-2xl font-semibold text-stone-900">11.5</dd></div>
      <div class="rounded-xl bg-white p-4 ring-1 ring-stone-900/5"><dt class="text-sm text-stone-600">Open slots</dt><dd class="mt-1 text-2xl font-semibold text-stone-900">6</dd></div>
    </dl>
    <div class="mt-4 rounded-xl bg-white p-4 ring-1 ring-stone-900/5">
      <h3 class="text-sm font-semibold text-stone-900">Deadlines</h3>
      <ul class="mt-2 divide-y divide-stone-100 text-sm">
        <li class="flex justify-between gap-3 py-2"><span class="text-stone-900">Harbor Co brand guide</span><span class="shrink-0 text-stone-600">Jun 18</span></li>
        <li class="flex justify-between gap-3 py-2"><span class="text-stone-900">Pricing page launch</span><span class="shrink-0 text-stone-600">Jun 24</span></li>
        <li class="flex justify-between gap-3 py-2"><span class="text-stone-900">Quarterly report</span><span class="shrink-0 text-stone-600">Jun 29</span></li>
      </ul>
    </div>
  </div>
</div>

Booking slots picker

  • has-checked:
  • has-disabled:
  • has-focus-visible:
  • sr-only
  • disabled:

Pick a day and a time, then confirm.

Days and times are real radio buttons inside labels, so arrow keys, form submission and screen readers work without ARIA. The inputs are sr-only, and the label styles itself from its input with has-checked:, has-disabled: and has-focus-visible:, a pattern that needs no peer ordering. The script rebuilds the time list for the chosen day with createElement, disables booked slots, enables the confirm button once a time is picked and reports the booking in a status line.

View code
<form id="tcl-sl" class="grid w-full max-w-3xl overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-gray-900/5 md:grid-cols-[1fr_1.1fr]">
  <div class="border-b border-gray-100 p-5 md:border-r md:border-b-0">
    <div class="flex items-center gap-3">
      <svg aria-hidden="true" viewBox="0 0 40 40" class="size-10 shrink-0"><circle cx="20" cy="20" r="20" fill="#1e3a8a"/><text x="20" y="25" text-anchor="middle" font-size="14" font-weight="600" fill="#fff" font-family="system-ui, sans-serif">NM</text></svg>
      <div>
        <p class="text-sm font-semibold text-gray-900">Dr. Nadia Marsh</p>
        <p class="text-sm text-gray-600">Northgate Dental</p>
      </div>
    </div>
    <h2 class="mt-4 text-lg font-semibold text-gray-900">Hygiene appointment</h2>
    <p class="mt-1 text-sm text-gray-600">45 minutes, in person, 18 Quay Street</p>
    <fieldset class="mt-5">
      <legend class="text-sm font-semibold text-gray-900">Week of June 14, 2027</legend>
      <div class="mt-2 grid grid-cols-5 gap-1.5">
        <label class="flex min-h-20 cursor-pointer flex-col items-center justify-center rounded-xl border border-gray-300 text-gray-900 hover:border-blue-700 has-checked:border-blue-700 has-checked:bg-blue-700 has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-blue-700 has-disabled:cursor-not-allowed has-disabled:border-gray-200 has-disabled:bg-gray-50 has-disabled:text-gray-400">
          <input type="radio" name="tcl-sl-day" value="2027-06-14" class="tcl-sl-day sr-only">
          <span class="text-xs">Mon<span class="sr-only">day, June 14</span></span>
          <span class="text-lg font-semibold tabular-nums" aria-hidden="true">14</span>
          <span class="text-[11px]">3 open</span>
        </label>
        <label class="flex min-h-20 cursor-pointer flex-col items-center justify-center rounded-xl border border-gray-300 text-gray-900 hover:border-blue-700 has-checked:border-blue-700 has-checked:bg-blue-700 has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-blue-700 has-disabled:cursor-not-allowed has-disabled:border-gray-200 has-disabled:bg-gray-50 has-disabled:text-gray-400">
          <input type="radio" name="tcl-sl-day" value="2027-06-15" class="tcl-sl-day sr-only" checked>
          <span class="text-xs">Tue<span class="sr-only">day, June 15</span></span>
          <span class="text-lg font-semibold tabular-nums" aria-hidden="true">15</span>
          <span class="text-[11px]">4 open</span>
        </label>
        <label class="flex min-h-20 cursor-pointer flex-col items-center justify-center rounded-xl border border-gray-300 text-gray-900 hover:border-blue-700 has-checked:border-blue-700 has-checked:bg-blue-700 has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-blue-700 has-disabled:cursor-not-allowed has-disabled:border-gray-200 has-disabled:bg-gray-50 has-disabled:text-gray-400">
          <input type="radio" name="tcl-sl-day" value="2027-06-16" class="tcl-sl-day sr-only">
          <span class="text-xs">Wed<span class="sr-only">day, June 16</span></span>
          <span class="text-lg font-semibold tabular-nums" aria-hidden="true">16</span>
          <span class="text-[11px]">2 open</span>
        </label>
        <label class="flex min-h-20 cursor-pointer flex-col items-center justify-center rounded-xl border border-gray-300 text-gray-900 hover:border-blue-700 has-checked:border-blue-700 has-checked:bg-blue-700 has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-blue-700 has-disabled:cursor-not-allowed has-disabled:border-gray-200 has-disabled:bg-gray-50 has-disabled:text-gray-400">
          <input type="radio" name="tcl-sl-day" value="2027-06-17" class="tcl-sl-day sr-only" disabled>
          <span class="text-xs">Thu<span class="sr-only">day, June 17</span></span>
          <span class="text-lg font-semibold tabular-nums" aria-hidden="true">17</span>
          <span class="text-[11px]">Full</span>
        </label>
        <label class="flex min-h-20 cursor-pointer flex-col items-center justify-center rounded-xl border border-gray-300 text-gray-900 hover:border-blue-700 has-checked:border-blue-700 has-checked:bg-blue-700 has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-blue-700 has-disabled:cursor-not-allowed has-disabled:border-gray-200 has-disabled:bg-gray-50 has-disabled:text-gray-400">
          <input type="radio" name="tcl-sl-day" value="2027-06-18" class="tcl-sl-day sr-only">
          <span class="text-xs">Fri<span class="sr-only">day, June 18</span></span>
          <span class="text-lg font-semibold tabular-nums" aria-hidden="true">18</span>
          <span class="text-[11px]">5 open</span>
        </label>
      </div>
    </fieldset>
  </div>
  <div class="flex flex-col p-5">
    <fieldset>
      <legend id="tcl-sl-legend" class="text-sm font-semibold text-gray-900">Times on Tuesday, June 15</legend>
      <div id="tcl-sl-times" class="mt-2 grid grid-cols-3 gap-2"></div>
    </fieldset>
    <p class="mt-3 text-xs text-gray-600">Times shown in Central European Summer Time (UTC+2).</p>
    <button type="submit" id="tcl-sl-confirm" disabled class="mt-5 min-h-11 rounded-lg bg-blue-700 px-5 text-sm font-semibold text-white hover:bg-blue-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-700 disabled:cursor-not-allowed disabled:bg-gray-200 disabled:text-gray-600">Confirm booking</button>
    <p id="tcl-sl-status" role="status" class="mt-3 text-sm font-medium text-emerald-800 empty:hidden"></p>
  </div>
</form>

Live month with ISO week numbers

  • has-aria-[current=date]:
  • table-fixed
  • th scope="row"
  • aria-[current=date]:
  • Intl.DateTimeFormat

This demo uses your device date. Use Previous, Next and Today.

The one demo on this page that reads the real date: it opens on the current month, marks today with aria-current="date" and adds ISO 8601 week numbers in a Monday-first table. A read-only month is tabular data, so it is a real table with th scope="row" week numbers, and has-aria-[current=date]: tints the whole row of the current week from the cell inside it, no row class needed. Previous, Next and Today buttons move the view.

View code
<div class="w-full max-w-sm rounded-2xl bg-white p-5 shadow-sm ring-1 ring-amber-900/10">
  <div class="flex items-center gap-1">
    <h2 id="tcl-lv-title" aria-live="polite" class="mr-auto text-base font-semibold text-stone-900">This month</h2>
    <button type="button" id="tcl-lv-today" class="min-h-9 rounded-lg px-3 text-sm font-semibold text-amber-800 hover:bg-amber-50 focus-visible:outline-2 focus-visible:outline-amber-700">Today</button>
    <button type="button" id="tcl-lv-prev" aria-label="Previous month" class="grid size-9 place-items-center rounded-lg text-stone-700 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-amber-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m12 5-5 5 5 5"/></svg></button>
    <button type="button" id="tcl-lv-next" aria-label="Next month" class="grid size-9 place-items-center rounded-lg text-stone-700 hover:bg-stone-100 focus-visible:outline-2 focus-visible:outline-amber-700"><svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="size-5"><path d="m8 5 5 5-5 5"/></svg></button>
  </div>
  <p id="tcl-lv-now" class="text-sm text-stone-600"></p>
  <table class="mt-4 w-full table-fixed border-collapse text-center text-sm" aria-labelledby="tcl-lv-title">
    <thead><tr id="tcl-lv-head" class="text-xs text-stone-600"></tr></thead>
    <tbody id="tcl-lv-body"></tbody>
  </table>
</div>

Dark mode, theme tokens and locales

Ways to restyle a calendar without touching its logic: dark: variants with a theme switch, a palette defined as prefixed @theme tokens, and Intl-driven names, digits, week starts and right-to-left layout.

Dark mode calendar with a theme switch

  • dark:
  • @custom-variant dark
  • role="switch"
  • group-aria-checked:
  • dark:aria-[current=date]:ring-lime-300

Flip the Dark switch.

Every color has a dark: partner, and dark: here is the class-based variant from @custom-variant dark (&:where(.dark, .dark *)), so the switch only toggles the dark class on the demo wrapper. Stacked variants such as dark:aria-[current=date]:ring-lime-300 restyle a state in one theme only. The switch is a button with role="switch" and aria-checked, and group-aria-checked: slides its knob; the dots keep enough contrast in both themes by changing shade, not just opacity.

View code
<div id="tcl-dk" class="dark w-full max-w-md">
  <div class="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-zinc-900/5 motion-safe:transition-colors dark:bg-zinc-900 dark:ring-white/10">
    <div class="flex items-start justify-between gap-3">
      <div>
        <h2 class="text-base font-semibold text-zinc-900 dark:text-white">Studio bookings</h2>
        <p class="text-sm text-zinc-600 dark:text-zinc-400">June 2027, Pier 9 Studios</p>
      </div>
      <button type="button" id="tcl-dk-switch" role="switch" aria-checked="true" class="group flex min-h-10 items-center gap-2 rounded-full px-1 text-sm font-semibold text-zinc-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-900 dark:text-zinc-200 dark:focus-visible:outline-lime-300">
        Dark
        <span aria-hidden="true" class="relative h-6 w-11 rounded-full bg-zinc-300 motion-safe:transition-colors group-aria-checked:bg-lime-300">
          <span class="absolute top-0.5 left-0.5 size-5 rounded-full bg-white shadow-sm motion-safe:transition-transform group-aria-checked:translate-x-5 group-aria-checked:bg-zinc-900"></span>
        </span>
      </button>
    </div>
    <div aria-hidden="true" class="mt-4 grid grid-cols-7 text-center text-xs font-semibold text-zinc-600 dark:text-zinc-400"><div>Sun</div><div>Mon</div><div>Tue</div><div>Wed</div><div>Thu</div><div>Fri</div><div>Sat</div></div>
    <ol class="mt-1 grid grid-cols-7 gap-1">
      <li aria-hidden="true" class="h-11 md:h-12"></li>
      <li aria-hidden="true" class="h-11 md:h-12"></li>
      <li><time datetime="2027-06-01" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">1<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-02" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">2<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-03" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">3<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-04" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">4<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span></span></time></li>
      <li><time datetime="2027-06-05" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">5<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-06" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">6<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-07" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">7<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-08" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">8<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-09" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">9<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-10" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">10<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-11" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">11<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-12" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">12<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-13" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">13<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-14" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">14<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-15" aria-current="date" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">15<span class="sr-only">, 3 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-16" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">16<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-17" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">17<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-18" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">18<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-19" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">19<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-20" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">20<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-21" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">21<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-22" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">22<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span></span></time></li>
      <li><time datetime="2027-06-23" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">23<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-24" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">24<span class="sr-only">, 2 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-25" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">25<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-26" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">26<span class="sr-only">, 3 sessions</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-lime-600 dark:bg-lime-300"></span><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li><time datetime="2027-06-27" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">27<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-28" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">28<span class="sr-only"></span><span aria-hidden="true" class="flex h-1.5 gap-0.5"></span></time></li>
      <li><time datetime="2027-06-29" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">29<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-rose-600 dark:bg-rose-400"></span></span></time></li>
      <li><time datetime="2027-06-30" class="flex h-11 flex-col items-center justify-center gap-1 rounded-lg text-sm text-zinc-900 tabular-nums md:h-12 dark:text-zinc-100 aria-[current=date]:font-bold aria-[current=date]:ring-2 aria-[current=date]:ring-zinc-900 dark:aria-[current=date]:ring-lime-300">30<span class="sr-only">, 1 session</span><span aria-hidden="true" class="flex h-1.5 gap-0.5"><span class="size-1.5 rounded-full bg-sky-600 dark:bg-sky-400"></span></span></time></li>
      <li aria-hidden="true" class="h-11 md:h-12"></li>
      <li aria-hidden="true" class="h-11 md:h-12"></li>
      <li aria-hidden="true" class="h-11 md:h-12"></li>
    </ol>
    <div class="mt-4 flex flex-wrap gap-x-4 gap-y-1 border-t border-zinc-100 pt-3 text-xs text-zinc-700 dark:border-white/10 dark:text-zinc-300">
      <span class="flex items-center gap-1.5"><span aria-hidden="true" class="size-2 rounded-full bg-lime-600 dark:bg-lime-300"></span>Photo shoot</span>
      <span class="flex items-center gap-1.5"><span aria-hidden="true" class="size-2 rounded-full bg-sky-600 dark:bg-sky-400"></span>Podcast</span>
      <span class="flex items-center gap-1.5"><span aria-hidden="true" class="size-2 rounded-full bg-rose-600 dark:bg-rose-400"></span>Rehearsal</span>
    </div>
  </div>
</div>

Wall calendar themed with @theme tokens

  • @theme
  • --color-tcl-*
  • --font-tcl-display
  • bg-tcl-paper
  • border-tcl-line

A printed-style wall calendar whose whole palette comes from five @theme variables. Tailwind turns --color-tcl-paper into bg-tcl-paper, text-tcl-paper, border-tcl-paper and the rest, and --font-tcl-display into font-tcl-display, so the markup reads like a design system. The names carry the set prefix because every demo on this page shares one stylesheet; in your project, --color-paper would do. Sundays and notes use the accent, and notes collapse to a dot plus sr-only text on phones.

View code
<article class="w-full max-w-md overflow-hidden rounded-md bg-tcl-paper font-tcl-display text-tcl-ink shadow-md ring-1 ring-tcl-line">
  <img src="fjord.webp" width="800" height="600" alt="A deep blue fjord winding between granite mountains, seen from a cliff plateau with hikers" class="aspect-[16/9] w-full object-cover">
  <div class="p-4 md:p-6">
    <div class="flex items-baseline justify-between border-b-2 border-tcl-ink pb-2">
      <h2 class="text-4xl tracking-tight">June</h2>
      <p class="text-lg text-tcl-muted tabular-nums">2027</p>
    </div>
    <p class="mt-2 text-sm text-tcl-muted italic">Preikestolen, Lysefjord</p>
    <div aria-hidden="true" class="mt-4 grid grid-cols-7 font-sans text-[11px] font-semibold tracking-[0.12em] text-tcl-muted uppercase"><div class="pb-1 pl-1 text-tcl-accent">Sun</div><div class="pb-1 pl-1">Mon</div><div class="pb-1 pl-1">Tue</div><div class="pb-1 pl-1">Wed</div><div class="pb-1 pl-1">Thu</div><div class="pb-1 pl-1">Fri</div><div class="pb-1 pl-1">Sat</div></div>
    <ol class="grid grid-cols-7 border-t border-l border-tcl-line">
      <li aria-hidden="true" class="h-11 border-r border-b border-tcl-line md:h-14"></li>
      <li aria-hidden="true" class="h-11 border-r border-b border-tcl-line md:h-14"></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-01" class="text-sm text-tcl-ink tabular-nums">1</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-02" class="text-sm text-tcl-ink tabular-nums">2</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-03" class="text-sm text-tcl-ink tabular-nums">3</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-04" class="text-sm text-tcl-ink tabular-nums">4</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-05" class="text-sm text-tcl-ink tabular-nums">5</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-06" class="text-sm text-tcl-accent tabular-nums">6</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-07" class="text-sm text-tcl-ink tabular-nums">7</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-08" class="text-sm text-tcl-ink tabular-nums">8</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-09" class="text-sm text-tcl-ink tabular-nums">9</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-10" class="text-sm text-tcl-ink tabular-nums">10</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-11" class="text-sm text-tcl-ink tabular-nums">11</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-12" class="text-sm text-tcl-ink tabular-nums">12</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-13" class="text-sm text-tcl-accent tabular-nums">13</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-14" class="text-sm text-tcl-ink tabular-nums">14</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-15" class="text-sm text-tcl-ink tabular-nums">15</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-16" class="text-sm text-tcl-ink tabular-nums">16</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-17" class="text-sm text-tcl-ink tabular-nums">17</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-18" class="text-sm text-tcl-ink tabular-nums">18</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-19" class="text-sm text-tcl-ink tabular-nums">19</time><span class="mt-auto hidden truncate text-[10px] text-tcl-accent italic md:block">Juneteenth</span><span aria-hidden="true" class="mt-auto size-1.5 rounded-full bg-tcl-accent md:hidden"></span><span class="sr-only md:hidden">, Juneteenth</span></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-20" class="text-sm text-tcl-accent tabular-nums">20</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-21" class="text-sm text-tcl-ink tabular-nums">21</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-22" class="text-sm text-tcl-ink tabular-nums">22</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-23" class="text-sm text-tcl-ink tabular-nums">23</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-24" class="text-sm text-tcl-ink tabular-nums">24</time><span class="mt-auto hidden truncate text-[10px] text-tcl-accent italic md:block">Midsummer fair</span><span aria-hidden="true" class="mt-auto size-1.5 rounded-full bg-tcl-accent md:hidden"></span><span class="sr-only md:hidden">, Midsummer fair</span></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-25" class="text-sm text-tcl-ink tabular-nums">25</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-26" class="text-sm text-tcl-ink tabular-nums">26</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-27" class="text-sm text-tcl-accent tabular-nums">27</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-28" class="text-sm text-tcl-ink tabular-nums">28</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-29" class="text-sm text-tcl-ink tabular-nums">29</time></li>
      <li class="flex h-11 min-w-0 flex-col border-r border-b border-tcl-line p-1 md:h-14 md:p-1.5"><time datetime="2027-06-30" class="text-sm text-tcl-ink tabular-nums">30</time><span class="mt-auto hidden truncate text-[10px] text-tcl-accent italic md:block">Rent due</span><span aria-hidden="true" class="mt-auto size-1.5 rounded-full bg-tcl-accent md:hidden"></span><span class="sr-only md:hidden">, Rent due</span></li>
      <li aria-hidden="true" class="h-11 border-r border-b border-tcl-line md:h-14"></li>
      <li aria-hidden="true" class="h-11 border-r border-b border-tcl-line md:h-14"></li>
      <li aria-hidden="true" class="h-11 border-r border-b border-tcl-line md:h-14"></li>
    </ol>
  </div>
</article>

Locale-aware calendar with RTL support

  • Intl.DateTimeFormat
  • Intl.NumberFormat
  • dir="rtl"
  • lang
  • appearance-none

Switch the locale, then try the arrow keys in the grid.

Nothing in the markup is English: month, weekday names, digits and the full date come from Intl.DateTimeFormat and Intl.NumberFormat for the chosen locale, the first day of the week follows the locale (Sunday for the US and Japan, Monday for most of Europe, Saturday for Egypt), and the wrapper gets lang and dir. In Arabic the grid flips to right-to-left by itself, because CSS grid follows the writing direction, and the script swaps ArrowLeft and ArrowRight to match. The select is appearance-none with its chevron stacked in the same grid cell.

View code
<div class="w-full max-w-sm rounded-2xl bg-white p-5 shadow-sm ring-1 ring-cyan-950/10">
  <label for="tcl-lc-locale" class="text-sm font-semibold text-slate-900">Language and region</label>
  <div class="mt-1.5 grid">
    <select id="tcl-lc-locale" class="col-start-1 row-start-1 min-h-11 w-full appearance-none rounded-lg border border-slate-300 bg-white pr-9 pl-3 text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-cyan-700">
      <option value="en-US" selected>English (United States)</option>
      <option value="en-GB">English (United Kingdom)</option>
      <option value="de-DE" lang="de">Deutsch (Deutschland)</option>
      <option value="fr-FR" lang="fr">Français (France)</option>
      <option value="ja-JP" lang="ja">日本語 (日本)</option>
      <option value="ar-EG" lang="ar">العربية (مصر)</option>
    </select>
    <svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" class="pointer-events-none col-start-1 row-start-1 mr-3 size-4 self-center justify-self-end text-slate-600"><path d="m6 8 4 4 4-4"/></svg>
  </div>
  <div id="tcl-lc-cal" lang="en-US" dir="ltr" class="mt-5">
    <h2 id="tcl-lc-title" aria-live="polite" class="text-center text-base font-semibold text-slate-900"></h2>
    <div id="tcl-lc-grid" role="grid" aria-labelledby="tcl-lc-title" class="mt-3 grid gap-1"></div>
    <p id="tcl-lc-out" aria-live="polite" class="mt-4 rounded-lg bg-cyan-50 px-3 py-2 text-center text-sm text-cyan-950"></p>
  </div>
</div>

What Is a Tailwind CSS Calendar?

Everything visual comes from the Tailwind framework and nothing else. There is no calendar class to add and no calendar plugin to install, so date logic comes from your own JavaScript or a library.

That split sets it apart from a standalone calendar widget. The markup lives in your project, which means every color, border and spacing value can be edited right in the class attribute.

Strip any version down and you find the same pieces.

  • A header with the month and year label and previous and next buttons
  • A row of seven short weekday names
  • One cell per date, laid out in seven columns
  • Trailing and leading dates from neighboring months
  • Styles for the today, selected, disabled and in-range states

The Tailwind team publishes its own calendar examples in month, week, day, year and double-month views, sold as part of Tailwind Plus (formerly Tailwind UI). Only existing license holders can get to them now.

Should You Use a Component Kit, a Headless Library or Build Your Own?

It depends on what the calendar has to do. A kit is the quick route when the layout is fixed. Real selection logic or an unusual layout is a different story, and those push you toward a headless library or your own grid.

First, decide whether you need a date picker or an event calendar. That alone removes half the options.

In practice it sorts out like this.

  • For a static month on a landing page, copy a kit block.
  • A date or range field in a form calls for a headless library such as React DayPicker, Cally or v-calendar, styled with utilities.
  • Appointments, week views and drag and drop mean an event library such as FullCalendar.
  • An unusual layout, or a hard rule of zero dependencies, is the case for a custom grid.

A Tailwind UI kit puts a calendar on screen in minutes. The catch comes after, because once the markup is pasted in, every later fix is yours to make.

React DayPicker maps each stylable element to a class name, which is why utilities attach cleanly (shadcn/ui’s Calendar passes them through a classNames prop). The library gives you behavior and nothing else, so you write every class and do the wiring yourself.

Building your own has a real argument behind it. A DEV Community walkthrough for Vue 3 and Day.js points out that UI libraries go unmaintained or break on major updates, and that CSS grid keeps a bare calendar short. Selection, keyboard handling and locales then become your code, which is the price.

Which Prebuilt Tailwind CSS Calendars Are Worth Using?

shadcn/ui Calendar is the default pick for React. Flowbite datepicker covers plain HTML. daisyUI only makes sense when a calendar library is already in the project and just needs a style layer.

Tailwind Plus used to top this list. Tailwind Labs announced on September 9, 2026 that it is joining Shopify and closing sign-ups for new Tailwind Plus customers, while existing customers keep access and Tailwind CSS stays MIT-licensed (Tailwind Labs, 2026).

OptionBuilt onCoversCost
Tailwind Plus calendarsTailwind CSS with React, Vue or HTML examplesMonth, week, day, year, double viewsPaid, one-time. Closed to new buyers
Flowbite datepickerVanilla JavaScriptInline and range date pickerFree, MIT
shadcn/ui CalendarReact DayPickerSingle, range and dropdown date pickerFree, copied into your repo
daisyUI calendarCSS over Cally, React Day Picker, Vanilla Calendar ProDate picker stylingFree plugin
Preline UI calendar templateTailwind CSSCalendar and scheduling appPremium

shadcn/ui Calendar

shadcn/ui Calendar wraps React DayPicker and copies the component into your repository through the shadcn CLI.

The Tailwind classes then live in components/ui/calendar.tsx, so edits happen in your own file. Origin UI (now coss ui) publishes further Calendar variants on the same DayPicker base.

The code sits in your repo, so there is no black box. The docs examples show single date, range and dropdown month layouts, and accessibility comes from DayPicker, which presents itself as an accessible calendar component.

On the downside, it is React only. It picks dates but does not schedule events.

Flowbite datepicker

Flowbite’s datepicker runs on vanilla JavaScript and Tailwind utility classes, so it drops into any stack. Its docs list inline calendars and a date range picker, plus options for autohide and custom formats. Positioning is configurable as well.

No framework is needed, and the range picker comes included. It is MIT licensed too (per the flowbite-datepicker repository).

The weak side is scope. It selects dates and shows no events, and the React port (tailwind-datepicker-react) is a community package, not an official one.

daisyUI calendar

daisyUI ships no calendar logic. Its calendar component styles third-party libraries, and the daisyUI 5 documentation lists Cally, React Day Picker and Vanilla Calendar Pro.

Cally is a web component that works in any framework, and daisyUI styles it with a single class. The trade-off is plain: you install and wire the calendar library yourself, and daisyUI only decides how it looks.

Other galleries

Creative Tim lists 12+ calendar components and TailGrids offers a calendar block with month, week, day and year views. Horizon UI covers React full and mini calendars. Material Tailwind PRO sells a React calendar template.

Two of the three main picks above sit on the same engine. shadcn/ui documents React DayPicker as its base and daisyUI lists it as a supported library, so the real choice between them is who owns the styles, not how the calendar behaves.

Which Date Library Should Drive the Calendar Grid?

For a single month view with no formatting or locale needs, the native Date object is enough. Anything bigger is a Day.js or date-fns decision. Day.js is the lighter fit for a small custom grid, and date-fns makes more sense when the project already uses it.

Whatever you pick has to give you the first day of the month, how many days it has, where the week starts, a way to shift months and localized names.

Native Date brings zero dependencies, but you work out month length yourself (day zero of next month gives the last day of this one) and handle the week start by hand.

Day.js is described by its maintainers as a 2 kB immutable library with a Moment.js-style API. Its startOf(‘month’), startOf(‘week’), add(1, ‘month’) and daysInMonth() calls cover a full month grid, and locales load only when imported.

The DEV Community Vue 3 walkthrough finds the leading days by measuring the gap between the start of the month and the start of that month’s first week. Month navigation is the same call run with plus or minus one month.

With date-fns every function imports separately. The date-fns v4.0 announcement (2024) cites Bundlephobia at 17.5 kB for the whole package, but a bundler ships only the functions you import.

React DayPicker lists date-fns (with @date-fns/tz) as a runtime dependency. A DayPicker-based calendar already carries it, so adding Day.js on top means two date libraries for one calendar.

Time zones are where plain Date objects fail, because the grid renders in the browser’s local zone. date-fns added first-class time zone support in v4.0 on September 16, 2024, through the @date-fns/tz package, and its functions accept an in option such as tz(“Asia/Singapore”).

Whichever library wins, keep the date math in one module. The grid components should receive a plain array of days and nothing more.

How to Build a Tailwind CSS Calendar From Scratch

Start with the grid and grid-cols-7, pad the first week, mark today and the selection, and add responsive and dark variants at the very end.

  1. Work out the visible month. You need the first day, the number of days and the week start.
  2. Render a container with grid and grid-cols-7, one weekday label row, then one cell per date.
  3. Prepend days from the previous month so the 1st lands under the correct weekday.
  4. Style the states: today, selected, outside days, disabled.
  5. Store the visible month in state and let the previous and next buttons shift it by one.
  6. Add breakpoint and dark: variants last.

Grid and Weekday Header

According to the Tailwind CSS v4 docs, grid-cols-7 generates repeat(7, minmax(0, 1fr)). That gives seven equal columns that can shrink below their content width, which is exactly what a week row needs.

A gap-px utility on a gray container background gives hairline cell borders without extra border classes. For a static month, a plain HTML calendar markup with these classes and no script does the job.

React and Vue follow the same pattern. The DEV walkthrough (December 2022) keeps the visible month in a Vue 3 ref, while Codementor’s React version (April 2022) takes the month and year as props and renders all twelve months at once, with no month navigation.

Leading Days and Today Highlight

Pad the first week with cells from the previous month. Each state then gets its own utilities.

  • Today gets font-semibold and an accent text color.
  • The selected date takes a filled accent background, white text and rounded-full.
  • Outside days get muted gray text.
  • Disabled days drop in opacity and pick up cursor-not-allowed.

Responsive and Dark Mode Variants

Tailwind’s breakpoints are mobile-first. Unprefixed utilities apply at every width, and a prefix such as md: applies at 48rem (768px) and above (Tailwind CSS docs, v4.3).

Those prefixes compile to media queries like @media (width >= 40rem) for sm:. A seven-column grid on a 320px phone leaves under 46px per cell before gaps and padding, so use compact day labels and switch to a week or agenda layout on small screens.

The dark: variant follows prefers-color-scheme by default. For a manual toggle, override it in your CSS with @custom-variant dark (&:where(.dark, .dark *)); and add the dark class to the html element (Tailwind CSS v4 docs).

Which Views and Interactions Can a Tailwind CSS Calendar Support?

A styled grid covers month views and date selection. Timed week and day views, event display and drag and drop need an event library such as FullCalendar or ilamy Calendar, and both now work with Tailwind.

Views

  • Month is the default everywhere, a seven-column grid.
  • Week and day views put time slots down one axis (FullCalendar names them timeGridWeek and timeGridDay).
  • FullCalendar has dayGridYear and multiMonthYear for year and multi-month views, and ilamy Calendar has a year view.
  • Agenda and list views are listDay, listWeek, listMonth and listYear in FullCalendar, and an agenda plugin in ilamy Calendar.

Resource and timeline views, with one row per room or person, are FullCalendar Premium features (FullCalendar pricing, 2026). A mini calendar for a sidebar needs no library at all, because any date picker doubles as one.

Events and Drag and Drop

Events turn a date grid into a scheduler, and that is where hand-built Tailwind calendars stop paying off.

FullCalendar keeps drag and drop, event resizing, date clicks and range selection in its interaction plugin. Plain event clicks work without it (FullCalendar docs, v7).

FullCalendar v7 (current release 7.1.3, October 16, 2026) added a theme system with Tailwind support, a Breezy theme inspired by Tailwind Plus, Shadcn support and dark mode in every theme. It no longer bundles its own CSS, so you import a theme stylesheet.

ilamy Calendar takes the React and shadcn/ui route. It has month, week, day and year views, drag and drop, resource views, and RFC 5545 recurring events through a plugin, all under an MIT licence. Its components use shadcn/ui token classes such as bg-background and border-border, so your design system supplies the look (ilamy Calendar README).

My rule of thumb is FullCalendar when the stack is Vue, Angular or plain JavaScript, and ilamy Calendar when the project is already React with shadcn/ui.

Date and Range Selection

React DayPicker offers single, multiple and range modes, and Cally has calendar-date, calendar-range and calendar-multi components. Disabled days work through matchers, a dayOfWeek matcher for weekends and a before matcher for past dates.

One gotcha. In range mode, DayPicker counts disabled days inside a selected range by default. The excludeDisabled prop resets the range instead (DayPicker docs).

How Well Do Tailwind CSS Calendars Work With React, Vue and Plain JavaScript?

React has by far the deepest choice. Vue has fewer actively maintained options, and plain JavaScript gets two small libraries, Flowbite datepicker and Cally.

FullCalendar’s own docs show 7.7 million monthly npm downloads for its React package against 850,000 for its Vue 3 package (October 2026). That is a single library, but it shows where the ecosystem weight sits.

StackOptionsWatch for
ReactReact DayPicker via shadcn/ui Calendar, FullCalendar, ilamy CalendarDayPicker v10 prefers the @daypicker/react package name
Vuev-calendar, FullCalendar Vue 3 package, custom Vue 3 gridFullCalendar v7 dropped Vue 2
Plain JavaScriptFlowbite datepicker, Cally, FullCalendar vanilla packageYou write the state handling

On the React side, get a working Tailwind CSS setup in a React project first, then add the calendar. ilamy Calendar’s examples include a Next.js integration, and DayPicker v10 keeps the old react-day-picker name available for compatibility.

Vue users get v-calendar, whose v3 line adds weekly views, repeating date ranges and a simplified time picker. Its styling runs through class overrides and CSS variables, so Tailwind classes apply as overrides.

Some third-party comparisons describe v-calendar as headless, but the official docs describe default styling plus overrides. Treat it as a styled library you restyle.

Without a framework, Cally still works because it is a web component. Alpine.js can hold the selected date in plain HTML, and the native popover attribute or a dialog element wraps the date field. In React or Vue, Headless UI from Tailwind Labs supplies those popover and dialog primitives.

Are Tailwind CSS Calendars Accessible?

Accessibility comes from the engine under the classes, not from Tailwind. React DayPicker and Cally ship keyboard and screen reader support, and so do React Aria based calendars, while a hand-built grid has only the behavior you write.

The W3C’s Authoring Practices date picker example shows how ARIA roles map to a calendar. The W3C marks its code as not intended for production, so follow it as a spec instead of pasting it.

  • The day table carries the grid role
  • Only one date button sits in the Tab sequence (roving tabindex)
  • aria-selected appears only on the selected date
  • The month heading is a polite live region, so screen readers announce month changes
  • Escape closes the dialog and returns focus to the trigger button

React DayPicker follows that pattern. Arrow keys move by day and week, Page Up and Page Down by month, Home and End jump to the week edges, and Shift stretches the jump to months or years (DayPicker docs, v10.0.2).

FullCalendar’s v7 changelog reports every calendar view passing Axe accessibility testing, apart from one false-positive scrollbar error. Today is marked with aria-current=”date”.

Untitled UI’s React calendar goes the React Aria route. It is built on React Aria and Internationalized Date, then styled with Tailwind CSS (Untitled UI docs).

Tailwind controls what users see. Use focus-visible ring utilities so keyboard focus is obvious, and style the selected date with the aria-selected: variant so the state lives in one attribute.

Muted outside-day text still needs enough color contrast, which DayPicker’s own accessibility tips list. They also advise regular screen reader testing.

The failure case is the div grid. Cells that are divs with click handlers never reach keyboard users, so one button per day is the minimum.

When a Tailwind CSS Calendar Falls Short

These calendars break in predictable places, mostly unstyled library markup, upgrades, older browsers and views locked behind a paywall.

FailureCauseFix
Library calendar renders unstyledTailwind v4 skips node_modulesRegister the package path with @source
Copied calendar breaks after upgradeDeprecated class keys removedUpdate the classNames mapping
Broken layout on old browsersTailwind v4 browser baselineRaise support or stay on v3.4
Resource or timeline view missingFullCalendar Premium onlyLicense it or pick another library

The unstyled case happens because Tailwind v4 skips node_modules, .gitignore entries, binary files and CSS files when it scans for class names. Flowbite’s datepicker docs add @source for the flowbite-datepicker folder, and the ilamy Calendar README does the same for its dist folder.

Upgrades are the next trap. DayPicker v10 removed deprecated classNames keys such as table, nav_button and day_selected, and older generated shadcn/ui Calendar files may still use them (DayPicker upgrade guide). FullCalendar v7 also removed its fc-* class names, so v6 custom CSS needs migrating (FullCalendar v7 changelog).

Tailwind CSS v4 depends on Chrome 111 (March 2023), Safari 16.4 (March 2023) and Firefox 128 (July 2024), according to its v4.3 compatibility docs. Visitors on older browsers get a broken layout, so projects that need cross-browser compatibility that far back stay on Tailwind 3.4.

Then there is money. FullCalendar’s standard edition is free under MIT, while timeline views, resource views and printer-friendly rendering start at $480 for Premium (FullCalendar pricing, 2026). For Premium, v7 moved fully open-source projects to AGPLv3, while closed-source for-profit projects need a commercial license.

Some limits sit outside the grid. Recurring events (RFC 5545 rules) and named time zones are library features, and utility classes add neither.

The Tailwind Labs announcement does not say whether Tailwind Plus will get new calendar blocks. Existing license holders should treat the current examples as final.

For a single date field, a native date input costs nothing, a point daisyUI’s own calendar docs make.

Tailwind CSS Calendar FAQ

Is a Tailwind CSS calendar the same as the calendar in the Tailwind app?

No. The Tailwind app is a separate social media scheduling tool whose dashboard calendar schedules posts. Its weekly view lets you drag posts between days, and its monthly view lets you move posts between days or into different time slots through a daily pop-up (Tailwind support docs).

It shares a name with the CSS framework and nothing else, which is why searches for the two collide.

How do I open the calendar in a popover as a date picker?

Wrap the calendar in a popover that a button toggles. In React, shadcn/ui pairs its Calendar with Popover, PopoverTrigger and PopoverContent, while daisyUI with Cally uses the native popover attribute plus CSS anchor positioning.

Flowbite skips the wrapper. Add its datepicker attribute to an input element, with Flowbite’s JavaScript loaded, and you’re done.

Can I add a time picker to the calendar?

Yes, but DayPicker ships no time picker of its own. Pair it with an input of type time and merge the chosen hours and minutes into the selected date with setHours and setMinutes from date-fns (DayPicker guide).

Flowbite offers a separate timepicker component, and v-calendar v3 includes a simplified time picker.

How do I change the first day of the week?

Set the weekStartsOn prop in React DayPicker, where 0 is Sunday and 1 is Monday. The locale prop shifts it too, because date-fns locales carry their own week start.

In a custom grid, pass the same start day into the leading-days calculation.

Does a Tailwind CSS calendar support Persian, Hijri or other non-Gregorian calendars?

Yes, through React DayPicker. Version 10 moved each non-Gregorian calendar into its own add-on package: @daypicker/persian, @daypicker/hijri, @daypicker/buddhist, @daypicker/ethiopic and @daypicker/hebrew, installed beside @daypicker/react (DayPicker upgrade guide).

The older react-day-picker/persian style imports were removed in v10.

Keeping a Tailwind CSS Calendar Working Through Upgrades

The grid markup is rarely what costs you. The dependency chain is, since Tailwind, the calendar library and the date library each ship breaking releases on separate schedules.

Pin all three versions first, then upgrade in this order.

  1. Tailwind first, since every other layer depends on its class generation.
  2. The calendar library next, because its class hooks change between majors.
  3. The date library last. The date math can sit behind one module, so it hurts least.

A custom grid trades library upgrades for ownership of the selection and keyboard code.

Chrome, Edge and Firefox already ship native Temporal, while Safari has it only in Technology Preview (October 2026). Once it reaches all modern browsers, the date library choice loses most of its weight. DayPicker’s scoped package name already leaves room for it, and FullCalendar v7 requires a Temporal polyfill today (verified October 23, 2026).

Once the calendar holds up, place it in a Tailwind dashboard layout, where scheduling views usually live.