Components / Form & Input / Calendar

Calendar stable

One month, standing on its own: a grid of real day buttons with today marked and a selection you can move.

Preview
Month
<div class="k-cal" data-cal>
  <div class="k-cal-h">
    <button class="k-cal-nav" type="button" data-cal-prev aria-label="Previous month"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
    <span class="k-cal-title" data-cal-title>Month</span>
    <button class="k-cal-nav" type="button" data-cal-next aria-label="Next month"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
  </div>
  <div class="k-cal-wd" aria-hidden="true"><span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span></div>
  <div class="k-cal-grid" data-cal-grid></div>
</div>
/* KATA Calendar — one month of real buttons. */
.k-cal{font-family:var(--k-font);color:var(--k-ink);background:var(--k-bg);border:1px solid var(--k-line);border-radius:var(--k-radius-lg);padding:14px;width:288px}
.k-cal-h{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.k-cal-title{font-size:14px;font-weight:600;letter-spacing:-.01em}
.k-cal-nav{width:30px;height:30px;border-radius:8px;border:1px solid var(--k-line);background:var(--k-bg);color:var(--k-ink-2);display:flex;align-items:center;justify-content:center;cursor:pointer}
.k-cal-nav:hover{background:var(--k-bg-soft);color:var(--k-ink)}
.k-cal-wd{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:4px}
.k-cal-wd span{text-align:center;font-size:11px;font-weight:600;color:var(--k-ink-3);padding:4px 0}
.k-cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.k-cal-day{height:34px;border:0;border-radius:8px;background:none;font-family:inherit;font-size:13px;color:var(--k-ink);cursor:pointer;position:relative}
.k-cal-day:hover{background:var(--k-bg-soft)}
.k-cal-day.off{color:var(--k-ink-3);opacity:.55}
.k-cal-day.today{font-weight:650}
.k-cal-day.today::after{content:"";position:absolute;left:50%;bottom:4px;transform:translateX(-50%);width:5px;height:5px;border-radius:50%;background:var(--k-core)}
.k-cal-day.sel{background:var(--k-ink);color:var(--k-bg);font-weight:600}
.k-cal-day.sel::after{background:var(--k-bg)}
.k-cal.sm{width:248px;padding:11px}
.k-cal.sm .k-cal-day{height:29px;font-size:12.5px}
// KATA Calendar — renders one month of day buttons; the pick is yours.
// Set data-selected="YYYY-MM-DD" to start with a selection. Emits a
// "kata:date" CustomEvent (detail: "YYYY-MM-DD") on the element per pick.
document.querySelectorAll('[data-cal]').forEach(function (el) {
  var title = el.querySelector('[data-cal-title]');
  var grid = el.querySelector('[data-cal-grid]');
  var sel = el.getAttribute('data-selected') || '';
  var now = new Date();
  var view = sel ? new Date(sel + 'T00:00:00') : new Date(now.getFullYear(), now.getMonth(), 1);
  view = new Date(view.getFullYear(), view.getMonth(), 1);
  var NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December'];
  var FULL = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
  var MON = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  function iso(d) { return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); }
  function render() {
    title.textContent = NAMES[view.getMonth()] + ' ' + view.getFullYear();
    grid.innerHTML = '';
    var first = new Date(view.getFullYear(), view.getMonth(), 1);
    var start = new Date(first); start.setDate(1 - first.getDay());
    for (var i = 0; i < 42; i++) {
      var d = new Date(start); d.setDate(start.getDate() + i);
      var b = document.createElement('button');
      b.type = 'button';
      b.className = 'k-cal-day' + (d.getMonth() !== view.getMonth() ? ' off' : '') + (iso(d) === iso(now) ? ' today' : '') + (iso(d) === sel ? ' sel' : '');
      b.textContent = d.getDate();
      b.setAttribute('aria-label', FULL[d.getDay()] + ', ' + MON[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear());
      if (iso(d) === iso(now)) b.setAttribute('aria-current', 'date');
      b.setAttribute('data-date', iso(d));
      grid.appendChild(b);
    }
  }
  grid.addEventListener('click', function (e) {
    var b = e.target.closest('.k-cal-day'); if (!b) return;
    sel = b.getAttribute('data-date');
    el.setAttribute('data-selected', sel);
    el.dispatchEvent(new CustomEvent('kata:date', { detail: sel, bubbles: true }));
    render();
  });
  el.querySelector('[data-cal-prev]').addEventListener('click', function () { view = new Date(view.getFullYear(), view.getMonth() - 1, 1); render(); });
  el.querySelector('[data-cal-next]').addEventListener('click', function () { view = new Date(view.getFullYear(), view.getMonth() + 1, 1); render(); });
  render();
});

Installation

CLI

node kata-ui.mjs add calendar

Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.

Manual

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.

Examples

With a selection

Month
<div class="k-cal" data-cal data-selected="2026-11-14">
  <div class="k-cal-h">
    <button class="k-cal-nav" type="button" data-cal-prev aria-label="Previous month"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
    <span class="k-cal-title" data-cal-title>Month</span>
    <button class="k-cal-nav" type="button" data-cal-next aria-label="Next month"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
  </div>
  <div class="k-cal-wd" aria-hidden="true"><span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span></div>
  <div class="k-cal-grid" data-cal-grid></div>
</div>

Compact

Month
<div class="k-cal sm" data-cal>
  <div class="k-cal-h">
    <button class="k-cal-nav" type="button" data-cal-prev aria-label="Previous month"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
    <span class="k-cal-title" data-cal-title>Month</span>
    <button class="k-cal-nav" type="button" data-cal-next aria-label="Next month"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="square" stroke-linejoin="bevel" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
  </div>
  <div class="k-cal-wd" aria-hidden="true"><span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span></div>
  <div class="k-cal-grid" data-cal-grid></div>
</div>

Usage

The Date Picker’s other half, unboxed — for booking pages, availability and anywhere the month itself is the interface. It renders one month and reports the pick; ranges, min/max and disabled days are your layer on top of its day buttons.

Accessibility

Days are buttons with full date labels ("November 14, 2026"), today carries aria-current, and the month title is a live heading the arrows update. Arrow-key grid navigation is the app’s layer; the tab stop per day is native.

Anatomy

The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.

.k-calclass
.k-cal-hclass
.k-cal-titleclass
.k-cal-navclass
.k-cal-wdclass
.k-cal-gridclass
.k-cal-dayclass
.offclass
.todayclass
.selclass
.smclass
data-caldata hook
data-cal-prevdata hook
data-cal-titledata hook
data-cal-nextdata hook
data-cal-griddata hook
data-selecteddata hook
data-datedata hook
aria-labelARIA
aria-hiddenARIA

Related parts