Open demo

CSS code

HTML
<div class="sheet" data-print>
  <div class="sheet-page">
    <div class="sheet-top"><h2 id="sheet-title" data-title></h2><span>uiCookies printable planner</span></div>
    <table class="sheet-grid" aria-labelledby="sheet-title">
      <thead><tr>
        <th scope="col"><span class="full">Monday</span><span class="abbr" aria-hidden="true">Mon</span></th>
        <th scope="col"><span class="full">Tuesday</span><span class="abbr" aria-hidden="true">Tue</span></th>
        <th scope="col"><span class="full">Wednesday</span><span class="abbr" aria-hidden="true">Wed</span></th>
        <th scope="col"><span class="full">Thursday</span><span class="abbr" aria-hidden="true">Thu</span></th>
        <th scope="col"><span class="full">Friday</span><span class="abbr" aria-hidden="true">Fri</span></th>
        <th scope="col"><span class="full">Saturday</span><span class="abbr" aria-hidden="true">Sat</span></th>
        <th scope="col"><span class="full">Sunday</span><span class="abbr" aria-hidden="true">Sun</span></th>
      </tr></thead>
      <tbody></tbody>
    </table>
    <div class="sheet-notes">
      <div><h3>Notes</h3><div class="sheet-lines" aria-hidden="true"><i></i><i></i><i></i></div></div>
      <div><h3>Top 3 this month</h3><div class="sheet-lines" aria-hidden="true"><i></i><i></i><i></i></div></div>
    </div>
  </div>
  <button type="button" class="sheet-print" data-print-btn>Print this month</button>
</div>
CSS
/* Printable planner: a month <table> with full weekday headers, ruled note areas and a print button.
   Below 640px the headers switch to three-letter names (the full name stays for screen readers). */
.sheet{position:relative;width:100%;max-width:860px;color:#17181b}
.sheet-page{background:#fff;padding:36px 40px;box-shadow:0 18px 44px -20px rgba(10,12,16,.3)}
.sheet-top{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;border-bottom:3px solid #17181b;padding-bottom:12px}
.sheet-top h2{font-size:1.6rem;letter-spacing:-.02em;text-transform:uppercase}
.sheet-top span{font-size:.8rem;color:#6b6e75}
.sheet-grid{width:100%;border-collapse:collapse;table-layout:fixed}
.sheet-grid th{padding:9px 8px;font-size:.68rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;text-align:left;
  border-bottom:1.5px solid #17181b}
.sheet-grid .abbr{display:none}
.sheet-grid td{height:92px;vertical-align:top;border-bottom:1px solid #d8dade;border-right:1px solid #d8dade;padding:6px 8px;
  font-size:.8rem;font-weight:700}
.sheet-grid td:last-child{border-right:0}
.sheet-grid td.dim{background:repeating-linear-gradient(45deg,#fafafb,#fafafb 6px,#f3f4f6 6px,#f3f4f6 12px)}
.sheet-grid td[aria-current="date"] .sheet-n{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;
  margin:-3px 0 0 -5px;border-radius:50%;background:#17181b;color:#fff}
.sheet-notes{margin-top:22px;display:grid;grid-template-columns:2fr 1fr;gap:24px}
.sheet-notes h3{font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px}
.sheet-lines{border-top:1px solid #d8dade}
.sheet-lines i{display:block;height:26px;border-bottom:1px solid #d8dade}
.sheet-print{position:fixed;bottom:22px;right:22px;border:0;background:#17181b;color:#fff;font:inherit;font-weight:700;font-size:.85rem;
  padding:12px 20px;border-radius:11px;cursor:pointer;box-shadow:0 10px 26px rgba(0,0,0,.3)}
.sheet-print:focus-visible{outline:2px solid #17181b;outline-offset:3px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@media (max-width:640px){
  .sheet-page{padding:22px 16px}
  .sheet-grid th{padding:9px 4px;letter-spacing:.04em}
  .sheet-grid .full{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
  .sheet-grid .abbr{display:inline}
  .sheet-grid td{height:64px;padding:5px}
  .sheet-notes{grid-template-columns:1fr}
}
@media print{
  .sheet{max-width:100%}
  .sheet-page{box-shadow:none;padding:0}
  .sheet-print{display:none}
  .sheet-grid td[aria-current="date"] .sheet-n{display:inline;margin:0;background:none;color:inherit}
}
JavaScript
/* Fills the grid with the current month (Monday first). Day cells carry the full date as hidden text and today has
   aria-current="date". The button calls window.print(); print styles drop the shadow, the button and the today mark. */
(() => {
  const root = document.querySelector('[data-print]'); if (!root) return;
  const CLS = { day: '', dim: 'dim', n: 'sheet-n', sr: 'sr-only' };
  const now = new Date(), y = now.getFullYear(), m = now.getMonth();
  const full = d => d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  root.querySelector('[data-title]').textContent = now.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
  const first = (new Date(y, m, 1).getDay() + 6) % 7, days = new Date(y, m + 1, 0).getDate();
  let h = '';
  for (let i = 0; i < 42; i++) {
    const d = i - first + 1;
    if (i % 7 === 0) h += '<tr>';
    h += d < 1 || d > days ? `<td class="${CLS.dim}"></td>`
      : `<td class="${CLS.day}"${d === now.getDate() ? ' aria-current="date"' : ''}><span class="${CLS.sr}">${full(new Date(y, m, d))}</span>` +
        `<span class="${CLS.n}" aria-hidden="true">${d}</span></td>`;
    if (i % 7 === 6) h += '</tr>';
  }
  root.querySelector('tbody').innerHTML = h;
  root.querySelector('[data-print-btn]').addEventListener('click', () => window.print());
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="sheet w-100" data-print>
  <div class="sheet-page bg-white">
    <div class="sheet-top d-flex justify-content-between align-items-baseline flex-wrap gap-3 pb-2"><h2 class="fw-bold text-uppercase mb-0" id="sheet-title" data-title></h2><span>uiCookies printable planner</span></div>
    <table class="table sheet-grid mb-0" aria-labelledby="sheet-title">
      <thead><tr>
        <th scope="col"><span class="full">Monday</span><span class="abbr" aria-hidden="true">Mon</span></th>
        <th scope="col"><span class="full">Tuesday</span><span class="abbr" aria-hidden="true">Tue</span></th>
        <th scope="col"><span class="full">Wednesday</span><span class="abbr" aria-hidden="true">Wed</span></th>
        <th scope="col"><span class="full">Thursday</span><span class="abbr" aria-hidden="true">Thu</span></th>
        <th scope="col"><span class="full">Friday</span><span class="abbr" aria-hidden="true">Fri</span></th>
        <th scope="col"><span class="full">Saturday</span><span class="abbr" aria-hidden="true">Sat</span></th>
        <th scope="col"><span class="full">Sunday</span><span class="abbr" aria-hidden="true">Sun</span></th>
      </tr></thead>
      <tbody></tbody>
    </table>
    <div class="sheet-notes mt-4">
      <div><h3 class="text-uppercase fw-bold mb-2">Notes</h3><div class="sheet-lines" aria-hidden="true"><i></i><i></i><i></i></div></div>
      <div><h3 class="text-uppercase fw-bold mb-2">Top 3 this month</h3><div class="sheet-lines" aria-hidden="true"><i></i><i></i><i></i></div></div>
    </div>
  </div>
  <button type="button" class="btn btn-dark sheet-print position-fixed d-print-none" data-print-btn>Print this month</button>
</div>
CSS
/* Bootstrap .table (border colour through --bs-table-border-color), .btn-dark for printing and the d-print-none utility.
   Below 640px the headers switch to three-letter names (the full name stays for screen readers). */
.sheet{max-width:860px;color:#17181b}
.sheet-page{padding:36px 40px;box-shadow:0 18px 44px -20px rgba(10,12,16,.3)}
.sheet-top{border-bottom:3px solid #17181b}
.sheet-top h2{font-size:1.6rem;letter-spacing:-.02em;line-height:normal}
.sheet-top span{font-size:.8rem;color:#6b6e75}
.sheet-grid{--bs-table-border-color:#d8dade;--bs-table-color:#17181b;table-layout:fixed}
.sheet-grid th{padding:9px 8px;font-size:.68rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;border-bottom:1.5px solid #17181b}
.sheet-grid .abbr{display:none}
.sheet-grid td{height:92px;vertical-align:top;padding:6px 8px;font-size:.8rem;font-weight:700;border-right:1px solid #d8dade}
.sheet-grid td:last-child{border-right:0}
.sheet-grid td.dim{--bs-table-bg:transparent;background:repeating-linear-gradient(45deg,#fafafb,#fafafb 6px,#f3f4f6 6px,#f3f4f6 12px)}
.sheet-grid td[aria-current="date"] .sheet-n{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;
  margin:-3px 0 0 -5px;border-radius:50%;background:#17181b;color:#fff}
.sheet-notes{display:grid;grid-template-columns:2fr 1fr;gap:24px}
.sheet-notes h3{font-size:.72rem;font-weight:800;letter-spacing:.1em;line-height:normal}
.sheet-lines{border-top:1px solid #d8dade}
.sheet-lines i{display:block;height:26px;border-bottom:1px solid #d8dade}
.sheet-print{--bs-btn-bg:#17181b;--bs-btn-border-color:#17181b;--bs-btn-hover-bg:#2a2c31;--bs-btn-hover-border-color:#2a2c31;
  --bs-btn-font-size:.85rem;--bs-btn-font-weight:700;--bs-btn-padding-x:20px;--bs-btn-padding-y:12px;--bs-btn-border-radius:11px;
  --bs-btn-line-height:normal;bottom:22px;right:22px;box-shadow:0 10px 26px rgba(0,0,0,.3)}
.sheet-print:focus-visible{outline:2px solid #17181b;outline-offset:3px}
@media (max-width:640px){
  .sheet-page{padding:22px 16px}
  .sheet-grid th{padding:9px 4px;letter-spacing:.04em}
  .sheet-grid .full{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
  .sheet-grid .abbr{display:inline}
  .sheet-grid td{height:64px;padding:5px}
  .sheet-notes{grid-template-columns:1fr}
}
@media print{
  .sheet{max-width:100%}
  .sheet-page{box-shadow:none;padding:0}
  .sheet-grid td[aria-current="date"] .sheet-n{display:inline;margin:0;background:none;color:inherit}
}
@media (prefers-reduced-motion:reduce){.sheet-print{transition:none}}
JavaScript
/* Fills the grid with the current month (Monday first). Day cells carry the full date as hidden text and today has
   aria-current="date". The button calls window.print(); print styles drop the shadow, the button and the today mark. */
(() => {
  const root = document.querySelector('[data-print]'); if (!root) return;
  const CLS = { day: '', dim: 'dim', n: 'sheet-n', sr: 'visually-hidden' };
  const now = new Date(), y = now.getFullYear(), m = now.getMonth();
  const full = d => d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  root.querySelector('[data-title]').textContent = now.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
  const first = (new Date(y, m, 1).getDay() + 6) % 7, days = new Date(y, m + 1, 0).getDate();
  let h = '';
  for (let i = 0; i < 42; i++) {
    const d = i - first + 1;
    if (i % 7 === 0) h += '<tr>';
    h += d < 1 || d > days ? `<td class="${CLS.dim}"></td>`
      : `<td class="${CLS.day}"${d === now.getDate() ? ' aria-current="date"' : ''}><span class="${CLS.sr}">${full(new Date(y, m, d))}</span>` +
        `<span class="${CLS.n}" aria-hidden="true">${d}</span></td>`;
    if (i % 7 === 6) h += '</tr>';
  }
  root.querySelector('tbody').innerHTML = h;
  root.querySelector('[data-print-btn]').addEventListener('click', () => window.print());
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="relative w-full max-w-[860px] text-sheet-ink print:max-w-full" data-print>
  <div class="bg-white px-10 py-9 shadow-[0_18px_44px_-20px_rgba(10,12,16,.3)] max-sm:px-4 max-sm:py-[22px] print:p-0 print:shadow-none">
    <div class="flex flex-wrap items-baseline justify-between gap-3 border-b-3 border-sheet-ink pb-3"><h2 class="text-[1.6rem] font-bold tracking-[-.02em] uppercase" id="sheet-title" data-title></h2><span class="text-[.8rem] text-[#6b6e75]">uiCookies printable planner</span></div>
    <table class="w-full table-fixed border-collapse" aria-labelledby="sheet-title">
      <thead class="text-left text-[.68rem] font-extrabold tracking-[.08em] uppercase max-sm:tracking-[.04em]"><tr>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Monday</span><span class="hidden max-sm:inline" aria-hidden="true">Mon</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Tuesday</span><span class="hidden max-sm:inline" aria-hidden="true">Tue</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Wednesday</span><span class="hidden max-sm:inline" aria-hidden="true">Wed</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Thursday</span><span class="hidden max-sm:inline" aria-hidden="true">Thu</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Friday</span><span class="hidden max-sm:inline" aria-hidden="true">Fri</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Saturday</span><span class="hidden max-sm:inline" aria-hidden="true">Sat</span></th>
        <th scope="col" class="border-b-[1.5px] border-sheet-ink px-2 py-[9px] max-sm:px-1"><span class="max-sm:sr-only">Sunday</span><span class="hidden max-sm:inline" aria-hidden="true">Sun</span></th>
      </tr></thead>
      <tbody></tbody>
    </table>
    <div class="mt-[22px] grid grid-cols-[2fr_1fr] gap-6 max-sm:grid-cols-1">
      <div><h3 class="mb-2 text-[.72rem] font-extrabold tracking-[.1em] uppercase">Notes</h3><div class="border-t border-sheet-rule [&>i]:block [&>i]:h-[26px] [&>i]:border-b [&>i]:border-sheet-rule" aria-hidden="true"><i></i><i></i><i></i></div></div>
      <div><h3 class="mb-2 text-[.72rem] font-extrabold tracking-[.1em] uppercase">Top 3 this month</h3><div class="border-t border-sheet-rule [&>i]:block [&>i]:h-[26px] [&>i]:border-b [&>i]:border-sheet-rule" aria-hidden="true"><i></i><i></i><i></i></div></div>
    </div>
  </div>
  <button type="button" class="fixed right-[22px] bottom-[22px] cursor-pointer rounded-[11px] bg-sheet-ink px-5 py-3 text-[.85rem] font-bold text-white shadow-[0_10px_26px_rgba(0,0,0,.3)] hover:bg-[#2a2c31] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-sheet-ink print:hidden" data-print-btn>Print this month</button>
</div>
CSS
@theme {
  --color-sheet-ink: #17181b;
  --color-sheet-rule: #d8dade;
}
JavaScript
/* Fills the grid with the current month (Monday first). Day cells carry the full date as hidden text and today has
   aria-current="date". The button calls window.print(); print styles drop the shadow, the button and the today mark. */
(() => {
  const root = document.querySelector('[data-print]'); if (!root) return;
  const CLS = { day: 'h-[92px] border-r border-b border-[#d8dade] px-2 py-1.5 align-top text-[.8rem] font-bold last:border-r-0 max-sm:h-16 max-sm:p-[5px] group', dim: 'h-[92px] border-r border-b border-[#d8dade] px-2 py-1.5 align-top text-[.8rem] font-bold last:border-r-0 max-sm:h-16 max-sm:p-[5px] bg-[repeating-linear-gradient(45deg,#fafafb,#fafafb_6px,#f3f4f6_6px,#f3f4f6_12px)]', n: 'group-aria-[current=date]:-mt-[3px] group-aria-[current=date]:-ml-[5px] group-aria-[current=date]:inline-flex group-aria-[current=date]:size-6 group-aria-[current=date]:items-center group-aria-[current=date]:justify-center group-aria-[current=date]:rounded-full group-aria-[current=date]:bg-sheet-ink group-aria-[current=date]:text-white print:group-aria-[current=date]:inline print:group-aria-[current=date]:m-0 print:group-aria-[current=date]:bg-transparent print:group-aria-[current=date]:text-inherit', sr: 'sr-only' };
  const now = new Date(), y = now.getFullYear(), m = now.getMonth();
  const full = d => d.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  root.querySelector('[data-title]').textContent = now.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
  const first = (new Date(y, m, 1).getDay() + 6) % 7, days = new Date(y, m + 1, 0).getDate();
  let h = '';
  for (let i = 0; i < 42; i++) {
    const d = i - first + 1;
    if (i % 7 === 0) h += '<tr>';
    h += d < 1 || d > days ? `<td class="${CLS.dim}"></td>`
      : `<td class="${CLS.day}"${d === now.getDate() ? ' aria-current="date"' : ''}><span class="${CLS.sr}">${full(new Date(y, m, d))}</span>` +
        `<span class="${CLS.n}" aria-hidden="true">${d}</span></td>`;
    if (i % 7 === 6) h += '</tr>';
  }
  root.querySelector('tbody').innerHTML = h;
  root.querySelector('[data-print-btn]').addEventListener('click', () => window.print());
})();

About this set

A monthly planner sheet that looks like paper on screen and prints cleanly, for printable downloads, teacher resources, habit trackers and office wall planners. The month name is set in large capitals over a heavy rule, the grid has full weekday names and tall, empty day boxes for handwriting, days outside the month are hatched with a fine diagonal pattern, and below the grid two ruled areas hold “Notes” and “Top 3 this month”. A floating “Print this month” button calls window.print(). Print styles remove the page shadow, the padding and the button and the today circle, so the paper copy is clean. The calendar fills itself in for the current month with a short script. On phones the sheet tightens its padding, the weekday headers switch to three-letter names while the full names stay available to screen readers, the boxes get shorter and the two note areas stack, so nothing overflows at 375 pixels. Today’s number is circled in black on screen. The Bootstrap version uses .table with a recoloured border variable, .btn-dark and the d-print-none utility; the Tailwind version uses print: and max-sm: variants.

What’s included

  • Paper-style monthly sheet with ruled note areas
  • Print styles hide the button, shadow and today mark
  • Hatched out-of-month days
  • Weekday headers shorten on phones, full names kept for screen readers
  • Current month generated automatically

Accessibility

The grid is a table labelled by the month heading. Weekday headers keep the full name as text and only switch to a visual abbreviation on small screens, while each day cell carries its full date as hidden text and today has aria-current="date". The ruled lines are aria-hidden decoration. The print button is a real button with a 2px focus outline, and the grey caption was darkened to pass 4.5:1.

Customise it

Change the ink colour #17181b, the rule colour #d8dade and the 92px day height in .sheet-grid td. In Bootstrap the rules come from --bs-table-border-color and the button from --bs-btn-* variables on .sheet-print; in Tailwind edit --color-sheet-ink and --color-sheet-rule.