Перейти к содержимому
Плотность
Тема

Отображение данных

Календарь

JS Выбор даты и переключение месяцев — слой приложения

Статическая сетка дат. Кит отвечает за то, чтобы сетка была сеткой, а сегодняшний день был помечен не только цветом.

Компонент
27 28 29 30 31 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 1 2 3 4 5 6
Разметка
<div class="inst-calendar" role="grid" aria-label="Август 2026">
  <span class="inst-calendar-dow" aria-hidden="true">пн</span>
  <span class="inst-calendar-dow" aria-hidden="true">вт</span>
  <span class="inst-calendar-dow" aria-hidden="true">ср</span>
  <span class="inst-calendar-dow" aria-hidden="true">чт</span>
  <span class="inst-calendar-dow" aria-hidden="true">пт</span>
  <span class="inst-calendar-dow" aria-hidden="true">сб</span>
  <span class="inst-calendar-dow" aria-hidden="true">вс</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">27</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">28</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">29</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">30</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">31</span>
  <span class="inst-calendar-day" role="gridcell">1</span>
  <span class="inst-calendar-day" role="gridcell">2</span>
  <span class="inst-calendar-day" role="gridcell">3</span>
  <span class="inst-calendar-day" role="gridcell">4</span>
  <span class="inst-calendar-day" role="gridcell">5</span>
  <span class="inst-calendar-day" role="gridcell">6</span>
  <span class="inst-calendar-day" role="gridcell">7</span>
  <span class="inst-calendar-day" role="gridcell">8</span>
  <span class="inst-calendar-day" role="gridcell">9</span>
  <span class="inst-calendar-day" role="gridcell">10</span>
  <span class="inst-calendar-day" role="gridcell">11</span>
  <span class="inst-calendar-day" role="gridcell" data-today="true">12</span>
  <span class="inst-calendar-day" role="gridcell">13</span>
  <span class="inst-calendar-day" role="gridcell">14</span>
  <span class="inst-calendar-day" role="gridcell">15</span>
  <span class="inst-calendar-day" role="gridcell">16</span>
  <span class="inst-calendar-day" role="gridcell">17</span>
  <span class="inst-calendar-day" role="gridcell" aria-selected="true">18</span>
  <span class="inst-calendar-day" role="gridcell">19</span>
  <span class="inst-calendar-day" role="gridcell">20</span>
  <span class="inst-calendar-day" role="gridcell">21</span>
  <span class="inst-calendar-day" role="gridcell" aria-disabled="true">22</span>
  <span class="inst-calendar-day" role="gridcell" aria-disabled="true">23</span>
  <span class="inst-calendar-day" role="gridcell">24</span>
  <span class="inst-calendar-day" role="gridcell">25</span>
  <span class="inst-calendar-day" role="gridcell">26</span>
  <span class="inst-calendar-day" role="gridcell">27</span>
  <span class="inst-calendar-day" role="gridcell">28</span>
  <span class="inst-calendar-day" role="gridcell" aria-disabled="true">29</span>
  <span class="inst-calendar-day" role="gridcell" aria-disabled="true">30</span>
  <span class="inst-calendar-day" role="gridcell">31</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">1</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">2</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">3</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">4</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">5</span>
  <span class="inst-calendar-day" role="gridcell" data-outside="true">6</span>
</div>

Использование

<div class="inst-calendar" role="grid" aria-label="Август 2026">
  <span class="inst-calendar-dow" aria-hidden="true">пн</span>
  …
  <span class="inst-calendar-day" role="gridcell"
        aria-label="2 августа 2026, суббота">2</span>
</div>
Что Обязательно Почему
role="grid" на сетке, role="gridcell" на дне да Без ролей календарь озвучивается как поток из 42 чисел
aria-label с месяцем и годом на сетке да «2» без месяца не является датой
aria-label с полной датой на каждом дне да Одно число вне контекста бесполезно
aria-hidden="true" на днях недели да «пн», «вт» дублируют информацию из полной даты ячейки

Когда использовать

Используйте Возьмите другое
Обзор месяца, где важна раскладка по неделям Ввод одной даты в форме<input type="date">, он стилизован китом и даёт нативный пикер с клавиатурой
Плотность событий по дням видна с одного взгляда Диапазон дат в фильтре — два нативных поля даты
Выбор дня внутри уже открытого месяца События во времени без сеткилента
Длительности параллельных процессовдорожки

Состояния

Атрибут Что значит
нет атрибутов Обычный день текущего месяца
data-today="true" Сегодня. Подчёркивание и полужирное начертание
data-outside="true" День соседнего месяца, добивающий неделю
aria-selected="true" Выбран
aria-disabled="true" Недоступен. Прозрачность 0.5, нажатие снято

Сегодня помечено подчёркиванием, а не только цветом. Это выполнение закона кита: цвет не имеет права быть единственным носителем состояния. Подчёркивание рисуется внутренней тенью толщиной --size-marker — той же, что несёт полосу активной вкладки и край текущего пункта навигации.

JS

Подключите модуль один раз на страницу — инициализировать компоненты по отдельности не нужно, кит работает делегированием и видит узлы, пришедшие позже.

<script type="module" src="src/kit.js"></script>

Что делает кит

Ничего. Календарь — это сетка дат, а даты кит не считает: месяц, високосный год, первый день недели и локаль — данные, а не оформление. Кит рисует сетку и состояния дня, разметку строит приложение.

Что должно сделать приложение

Что Что для этого есть в ките
Построить сетку месяца Классы дня и aria-label с полной датой
Отметить выбранное aria-selected="true" на дне
Переключить месяц Кнопки шапки — обычные <button>
grid.addEventListener('click', (e) => {
  const day = e.target.closest('[data-date]');
  if (!day || day.getAttribute('aria-disabled') === 'true') return;

  for (const d of grid.querySelectorAll('[aria-selected="true"]')) {
    d.setAttribute('aria-selected', 'false');
  }
  day.setAttribute('aria-selected', 'true');
  onPick(day.dataset.date);
});

Дата хранится в data-date в формате ISO, а не читается из подписи: подпись зависит от локали, и парсить её обратно — способ получить разъезд на первом же языке.

Правила

Так Сегодня помечено начертанием

Подчёркивание и полужирное — два признака помимо цвета. Скринридеру это сообщается словом в aria-label.

Не так Календарь для ввода одной даты

<input type="date"> уже стилизован китом и даёт нативный пикер с клавиатурой и мобильным колесом.

Так Полная дата в aria-label дня

«2 августа 2026, суббота». Одно число вне контекста ничего не значит.

Не так Сетка без ролей

Без role="grid" и role="gridcell" это поток из 42 чисел.

Доступность

Роли role="grid" на сетке, role="gridcell" на дне, role="row" на неделе. Без них календарь озвучивается как поток из 42 чисел
Имя aria-label с месяцем и годом на сетке. «2» без месяца не является датой
Дни недели aria-hidden="true": сокращения «пн», «вт» дублируют информацию, которую скринридер берёт из полной даты ячейки
Полная дата Каждый день несёт aria-label с полной датой («2 августа 2026, суббота»). Одно число вне контекста бесполезно
Клавиатура Стрелки по дням, PageUp/PageDown по месяцам, Home/End по неделе, бегущий tabindex. Поведение — на приложении
Сегодня Не только цвет: подчёркивание плюс начертание. Скринридеру это надо сообщить словом в aria-label
Цель нажатия Высота — --control-h-sm, то есть 26px. В плотности compact — 22px, ниже 24px по WCAG 2.5.8
Табличные цифры Включены, поэтому колонки чисел стоят ровно независимо от разрядности

API

ИмяЗначениеЧто делает
класс
inst-calendarСетка на семь колонок
inst-calendar-dowШапка: сокращение дня недели
inst-calendar-dayЯчейка дня
атрибут
data-todaytrueна inst-calendar-day
data-outsidetrueтам же
aria-selectedtrue · falseтам же
aria-disabledtrueтам же
токен
--space-12px
--space-24px
--control-h-sm26px
--radius-sm5px
--text-xs0.75rem
--text-2xs0.6875rem
--text-mutedlight-dark(var(--n-8), var(--n-6))
--surface-hoverlight-dark(oklch(0 0 0 / 0.035), oklch(1 0 0 / 0.045))
--surface-selectedcolor-mix(in oklab, var(--a-4) 14%, transparent)
--accent-textlight-dark(var(--a-5), var(--a-3))
--accent-solidvar(--a-4)
--size-marker2px
--weight-medium500

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument