Calendar
A date field component that allows users to enter and edit dates.
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
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 devalue, ou janvier 2026 en fallback. La nav prev/next émetdata-ui="dp-nav"avec le nouveaudata-month; l'island reconstruit le body viarebuildCalendar(). Le dropdowncaptionLayout="dropdown"(défaut) rend deux<select>natifs mois + année posés enopacity-0par-dessus le label — sinoncaptionLayout="label"pour un titre statique. - Sélection —
value="yyyy-mm-dd"pour single, ouvalue + valueEndpour un range (les jours entre les deux sont surlignés en accent). Deux<Calendar>côte à côte partageant la même pairevalue/valueEndforment un range picker sur deux mois. - Locale —
"fr"(défaut) ou"en". Affecte les libellés jours (lu ma me je ve sa divsSu 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
$ bext ui add calendar
Copy and paste the following into src/components/ui/calendar.tsx.
This component is interactive — make sure the shared island is loaded (see Islands). It reacts to data-ui="calendar" hooks.
Usage
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)
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
Sans les jours des mois adjacents
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
En anglais avec semaine commençant le dimanche
| Sa | Su | Mo | Tu | We | Th | Fr |
|---|---|---|---|---|---|---|
Bornes minDate / maxDate
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
Caption statique (sans dropdowns)
| lu | ma | me | je | ve | sa | di |
|---|---|---|---|---|---|---|
Edit this component live in the bext playground. The PRISM + signals version is the bext-native idiom — fine-grained reactivity, no virtual DOM.
On This Page