bx

Calendar

A date field component that allows users to enter and edit dates.

juin
2026
lumamejevesadi
tsx
<Calendar
  mode="single"
  month="2026-06"
  value="2026-06-13"
  locale="fr"
  captionLayout="dropdown"
  className="rounded-md border shadow-sm"
/>

Calendar est une grille de mois pure server-render. Chaque cellule est un bouton data-ui="dp-day" avec un data-date="yyyy-mm-dd" — l'island ui.js l'intercepte au clic pour bubbler la sélection au parent (DatePicker ou un form field custom).

Trois axes de contrôle :

  • Mois affichémonth="yyyy-mm". Défaut : le mois de value, ou janvier 2026 en fallback. La nav prev/next émet data-ui="dp-nav" avec le nouveau data-month ; l'island reconstruit le body via rebuildCalendar(). Le dropdown captionLayout="dropdown" (défaut) rend deux <select> natifs mois + année posés en opacity-0 par-dessus le label — sinon captionLayout="label" pour un titre statique.
  • Sélectionvalue="yyyy-mm-dd" pour single, ou value + valueEnd pour un range (les jours entre les deux sont surlignés en accent). Deux <Calendar> côte à côte partageant la même paire value/valueEnd forment un range picker sur deux mois.
  • Locale"fr" (défaut) ou "en". Affecte les libellés jours (lu ma me je ve sa di vs Su Mo Tu We Th Fr Sa) et les noms de mois. weekStart={0} commence la semaine dimanche (défaut lundi).

Options complémentaires : minDate / maxDate (bornes ISO, jours hors bornes désactivés + opacité 30%) ; showOutsideDays={false} pour ne pas rendre les jours du mois précédent/suivant (défaut : les rend en gris).

Piège vérifié : les classes utilisées par l'island runtime (grid-cols-7, size-N) ne sont pas générées par Tailwind Rust en mode route_css_complete. Le composant Calendar embarque un probe caché qui ancre ces classes dans le bundle scanné — ne pas retirer.

Installation

Terminal
$ bext ui add calendar

Usage

tsx
import { Calendar } from "@bext-stack/ui/primitives"

// Single date, mois affiché explicite, jour sélectionné :
<Calendar
  month="2026-06"
  value="2026-06-13"
  locale="fr"
  className="rounded-lg border"
/>

// Range (2 mois côte à côte) : partager value + valueEnd sur deux instances.
<div className="rounded-lg border inline-flex">
  <Calendar month="2026-01" value="2026-01-12" valueEnd="2026-02-11" />
  <Calendar month="2026-02" value="2026-01-12" valueEnd="2026-02-11" />
</div>

Anatomy

<Calendar>
  ├── nav (absolute inset-x-0 top-0)          // prev/next arrows
  │   ├── <button data-ui="dp-nav" data-month="prev-yyyy-mm">‹</button>
  │   └── <button data-ui="dp-nav" data-month="next-yyyy-mm">›</button>
  └── month_caption                            // titre du mois
      ├── captionLayout="dropdown" (défaut)
      │   ├── <select data-ui="dp-select-month">   // mois (opacity-0)
      │   └── <select data-ui="dp-select-year">    // année (opacity-0)
      └── captionLayout="label"
          └── <div data-ui="dp-label">Juin 2026</div>

  <table> (border-collapse)                   // la grille
    ├── <thead><tr>weekdays (lu ma me je ve sa di)</tr>
    └── <tbody data-ui="dp-grid">
        └── 6 <tr class="mt-2 flex w-full">
            └── 7 <td>
                └── <button data-ui="dp-day" data-date="yyyy-mm-dd">N</button>

API Reference

Calendar

Prop Type Default Description
value string Date sélectionnée au format ISO "yyyy-mm-dd".
valueEnd string Fin de range au format ISO. Si présent avec value, les jours entre les deux sont surlignés bg-accent.
month string mois de value ou "2026-01" Mois affiché au format "yyyy-mm". Détermine l'état initial ; le user peut naviguer ailleurs via l'island.
locale "fr" | "en" "fr" Locale des libellés jours (lu ma me je ve sa di / Su Mo Tu We Th Fr Sa) et noms de mois.
weekStart 0 | 1 1 Premier jour de la semaine. 0 = dimanche (US), 1 = lundi (FR/ISO).
captionLayout "dropdown" | "label" "dropdown" "dropdown" (défaut) : deux
fromYear number year - 100 Année la plus basse dans le dropdown année.
toYear number year + 10 Année la plus haute dans le dropdown année.
minDate string Borne basse ISO. Jours antérieurs = disabled + opacity-30.
maxDate string Borne haute ISO. Jours postérieurs = disabled.
showOutsideDays boolean true Rendre les jours du mois précédent/suivant en gris (permet de cliquer et de naviguer au mois adjacent).
className string Classes CSS supplémentaires sur le root. Typique : "rounded-lg border shadow-sm" quand utilisé standalone (hors popover).

Data Attributes

Attributs qu'on peut cibler en CSS custom pour override le style par état.

Attribute On Values Description
data-ui="dp-calendar" root Marqueur pour retrouver le Calendar depuis l'island. data-month="yyyy-mm" reflète le mois affiché.
data-ui="dp-nav" boutons prev/next data-month="yyyy-mm" Cliquer déclenche rebuildCalendar() au mois indiqué, sans reload.
data-ui="dp-select-month/year" 0..11 / year Change → rebuildCalendar au mois/année choisi.
data-ui="dp-day" chaque jour data-date="yyyy-mm-dd" Cliquer déclenche la sélection. Bubblé au parent DatePicker s'il y en a un.
data-outside="1" boutons dp-day présent quand outside Jour du mois adjacent (grisé). Cliquer nav vers ce mois puis sélectionne.
data-selected="true" quand jour sélectionné Utilisé par les sélecteurs CSS shadcn pour les corners arrondis dans un range.

Keyboard

Key Action
Tab Focus prev nav / dropdowns mois / dropdown année / next nav / grille.
Enter / Space Sur bouton prev/next : navigue. Sur bouton jour : sélectionne.
Arrow keys Sur

Accessibility

Le Calendar utilise une vraie <table> avec <thead> (jours de semaine) + <tbody> (dates). Les lecteurs d'écran annoncent naturellement la structure. Les jours de semaine sont dans <th>, chaque date dans un <td>.

Les boutons de navigation prev/next portent aria-label="Mois précédent" / "Mois suivant". Les dropdowns mois et année sont de vrais <select> natifs (accessibles clavier + AT sans JS custom), stylés en opacity-0 par-dessus un label visible.

Les jours désactivés (hors bornes minDate/maxDate) portent disabled aria-disabled="true". Les jours outside (mois adjacent) restent focusables — cliquer navigue vers ce mois avant de sélectionner.

Pour un vrai composant date-picker composable, envelopper dans un <DatePicker> qui apporte le trigger + hidden input pour form submission (voir DatePicker).

Examples

Range (deux mois côte à côte)

janvier
2026
lumamejevesadi
février
2026
lumamejevesadi
tsx
// Deux <Calendar> partageant la même paire value/valueEnd. Le range est
// mis en évidence entre les deux jours sur les deux mois.
<div className="rounded-lg border inline-flex">
  <Calendar month="2026-01" value="2026-01-12" valueEnd="2026-02-11" />
  <Calendar month="2026-02" value="2026-01-12" valueEnd="2026-02-11" />
</div>

Sans les jours des mois adjacents

août
2026
lumamejevesadi
tsx
<Calendar
  month="2026-08"
  value="2026-08-05"
  showOutsideDays={false}
  className="rounded-lg border"
/>

En anglais avec semaine commençant le dimanche

June
2026
SaSuMoTuWeThFr
tsx
<Calendar
  month="2026-06"
  value="2026-06-13"
  locale="en"
  weekStart={0}
  className="rounded-lg border"
/>

Bornes minDate / maxDate

juin
2026
lumamejevesadi
tsx
// Les jours hors bornes sont désactivés (opacity-30 + pointer-events-none).
<Calendar
  month="2026-06"
  value="2026-06-13"
  minDate="2026-06-05"
  maxDate="2026-06-25"
  className="rounded-lg border"
/>

Caption statique (sans dropdowns)

juin 2026
lumamejevesadi
tsx
// captionLayout="label" : le titre du mois est un simple texte au lieu
// des deux <select> mois + année.
<Calendar month="2026-06" value="2026-06-13" captionLayout="label" />
Try it live
Open in play

Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom — fine-grained reactivity, no virtual DOM.

src/app/page.tsx 2 files · signals · runs in your browser
Pure PRISM + real bext signals — fine-grained, no re-render.