PlCalendar
A month, on the page rather than in a popup. The same grid a PlDatePicker opens, with the trigger and the popup taken away.
import { PlCalendar } from 'plass-ui';
<PlCalendar value={day} onValueChange={setDay} />;import 'package:plass_ui/plass_ui.dart';
PlCalendar(
value: day,
onChanged: (DateTime? next) => setState(() => day = next),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'glass' | 'ghost' | 'glass' | What the sheet is made of. ghost when it is already inside something that draws one |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Cell, radius and type scale together. There is no density |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The family the chosen day, the today marker and the focus ring take |
| elevationshared | 0 | 1 | 2 | 3 | 1 | Drop shadow depth. 0 means no shadow at all |
| value | Date | null | — | The chosen day. Use with onValueChange for a controlled calendar |
| defaultValue | Date | null | — | The day it starts on, for an uncontrolled one |
| onValueChange | (value: Date | null) => void | — | Called when a day is chosen |
| precision | 'day' | 'month' | 'year' | 'day' | The smallest unit it hands back. A floor rather than a starting view, and the value is normalised to the start of it |
| month | Date | — | The month on screen. Use with onMonthChange to control it |
| defaultMonth | Date | — | The month it opens on. Defaults to the value's, or this month |
| onMonthChange | (month: Date) => void | — | Called when the month on screen changes |
| minDate | Date | null | — | Nothing before this day can be chosen. Read at the calendar's precision |
| maxDate | Date | null | — | Nothing after it can be chosen. Read at the calendar's precision |
| shouldDisableDate | (date: Date) => boolean | — | Blocks individual days — weekends, holidays, a booked date. Day-granular, so month and year never consult it |
| locale | string | — | The BCP 47 tag the month names, weekday initials and first day of the week come from. The page's own by default |
| weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | — | Which day the week starts on, as Date counts them — Sunday is 0. Worked out from locale when absent |
| showOutsideDays | boolean | true | Draws the leading and trailing days belonging to the neighbouring months |
| autoFocus | boolean | false | Takes the focus on mount. Off, because a calendar in a page is not a popup |
| disabled | boolean | false | Greys the whole calendar and takes it out of the tab order with inert. There is no readOnly |
| name | string | — | Submits with a form. The spelling follows precision — YYYY-MM-DD, YYYY-MM, YYYY |
| labels | Partial<PlPickerLabels> | — | The strings Intl has no opinion about — the buttons and the headings |
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | DateTime? | — | The chosen day, or null for none. Controlled, like every other input in the package |
| onChanged | ValueChanged<DateTime?>? | — | Called with the day that was taken. A null onChanged makes it inert |
| precision | PlCalendarPrecision | PlCalendarPrecision.day | The smallest unit it hands back. A floor rather than a starting view, and the value is normalised to the start of it |
| month | DateTime? | — | The month on screen. Use with onMonthChange to control it |
| defaultMonth | DateTime? | — | The month it opens on. Defaults to the value's, or this month |
| onMonthChanged | ValueChanged<DateTime>? | — | Called when the month on screen changes |
| minDate | DateTime? | — | Nothing before this day can be chosen. Read at the calendar's precision |
| maxDate | DateTime? | — | Nothing after it can be chosen. Read at the calendar's precision |
| shouldDisableDate | bool Function(DateTime)? | — | Blocks individual days — weekends, holidays, a booked date. Day-granular, so month and year never consult it |
| weekStartsOn | PlassWeekday? | — | Which day the week starts on, as Date counts them — Sunday is 0. Worked out from locale when absent |
| names | PlDateNames | PlDateNames.english | The words the calendar draws — the months, the weekdays. This is what a locale string is in the React build |
| labels | PlPickerLabels | PlPickerLabels.english | The words it says about itself — the steppers, the headings |
| showOutsideDays | bool | true | Draws the leading and trailing days belonging to the neighbouring months |
| autofocus | bool | false | Takes the focus on mount. Off, because a calendar in a page is not a popup |
| disabled | bool | false | Greys the whole calendar and takes it out of the tab order with inert. There is no readOnly |
| variantshared | PlassVariant | PlassVariant.glass | What the sheet is made of. ghost when it is already inside something that draws one |
| sizeshared | PlassSize | PlassSize.md | Cell, radius and type scale together. There is no density |
| colorshared | PlassColor | PlassColor.primary | The family the chosen day, the today marker and the focus ring take |
| elevationshared | int | 1 | Drop shadow depth. 0 means no shadow at all |
| semanticLabel | String? | — | The name a screen reader gives the whole grid |
Every native <div> attribute passes straight through. There is no label, description or error: this is not a field, so it has no text around it. Put it in a PlFieldset if it needs a caption.
There is no label, description or error: this is not a field, so it has no text around it. Put it in a PlFieldset if it needs a caption.
Two differences from the React build, both the ones every date component in this package has. names and labels take the words rather than a locale string, because the framework ships no Intl. English is the default, and an app that already has package:intl builds a PlDateNames from it in three lines. And there is no name: a Dart form is not an HTML one, so there is no hidden input to submit and the value is the caller's to send.
density is not offered. Padding on a grid of forty-two squares is what makes them stop being squares; size moves the whole ladder together instead. What the shared axes mean across the library is in prop conventions.
PlCalendar or PlDatePicker
A PlDatePicker is a field that happens to open a calendar: it belongs in a form, beside other fields, and its answer is one line of text until you open it. This is a calendar that is not standing in for a field, a booking page, an availability view, a date rail in a dashboard. The grid is the interface rather than a way of filling one in.
If the answer sits in a form next to other inputs, use the picker.
Examples
precision
A floor rather than a starting view. At month the month grid is the last grid and pressing a cell in it answers, so there is no day grid under it at all. A card's expiry is a month, and a control that made somebody answer which day of December 2027 is one that will be answered wrongly.
The value is normalised to the start of what was chosen: the 1st of the month, the 1st of January, never whichever day the cursor was resting on.
minDate, maxDate and shouldDisableDate
The two bounds are read at the calendar's own precision, so a minDate of 15 July leaves July pickable on a month calendar. shouldDisableDate is day-granular and is not consulted at all on the other two.
variant
glass by default, with the sheet and the elevation a PlCard has. Reach for ghost when the calendar is already inside something that draws a sheet. A second bordered rectangle inside the first is a second rectangle.
Controlling the month
month and onMonthChange control what is on screen independently of what is chosen, which is what two calendars kept a month apart need.
const [month, setMonth] = useState(startOfMonth(new Date()));
<PlCalendar month={month} onMonthChange={setMonth} value={day} onValueChange={setDay} />;Uncontrolled, the month follows the value: choosing a day out of a trailing week moves the grid to that day's month, because a selection in a month that is no longer on screen is one nobody can see.
In a form
name adds a hidden input. The spelling follows precision, YYYY-MM-DD, then YYYY-MM and YYYY, which is what the native inputs of the same shape submit.
<form action={book}>
<PlCalendar name="departure" />
</form>There is nothing to add. A Dart form is not an HTML one, so there is no hidden input and no name to give it. The value arrives in onChanged and sending it is the caller's.
disabled
Greys the calendar and takes it out of reach with the inert attribute, one attribute rather than a disabled on forty-two cells.
There is no readOnly beside it, and that is not an omission: a read-only field still shows a value a reader can select and copy, and a calendar has nothing to copy. To block some days rather than all of them, use shouldDisableDate.
Accessibility
- A real
role="grid"with one roving tab stop, so Tab leaves the calendar rather than walking forty-two cells. That is the pattern the ARIA date-picker practice describes, and the reason no cell is adisabledbutton. A blocked day isaria-disabledand still reachable, so a keyboard reader can find out that it is blocked. - The arrow keys move by one cell, PageUp/PageDown by a month (a year with Shift), Home/End to the ends of the week. Running off an edge steps the calendar rather than stopping.
- Each cell's accessible name is the full date in the calendar's
locale, so a screen reader reads "Monday 27 July 2026" rather than "27". autoFocusis off by default, the opposite of the picker's: a popup has just been opened by somebody who wants to be in it, and a calendar in a page has not.