Docs Components Data Display

Calendar

React example follows below.
ts
const calendarFixture = {
id: "calendar",
// Pinned, never `new Date()`: the demo has to render the same month, the
// same selected cell and the same today-ring in every framework, on every
// machine, forever. June 2026 also spans a full six-week grid with both
// leading and trailing spillover days, so the dimmed out-of-month cells are
// visible in the demo.
selectedIsoDay: "2026-06-18",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { Calendar } from "@grassroot/ui-react";
export default function CalendarDemo() {
return (
<div>
<Calendar
defaultValue={isoDayToDate(calendarFixture.selectedIsoDay)}
today={isoDayToDate(calendarFixture.todayIsoDay)}
/>
</div>
);
}
ts
<script setup lang="ts">
import { ref } from "vue";
const calendarFixture = {
id: "calendar",
// Pinned, never `new Date()`: the demo has to render the same month, the
// same selected cell and the same today-ring in every framework, on every
// machine, forever. June 2026 also spans a full six-week grid with both
// leading and trailing spillover days, so the dimmed out-of-month cells are
// visible in the demo.
selectedIsoDay: "2026-06-18",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { Calendar } from "@grassroot/ui-vue";
const selected = ref(isoDayToDate(calendarFixture.selectedIsoDay));
const today = isoDayToDate(calendarFixture.todayIsoDay);
</script>
<template>
<div><Calendar v-model="selected" :today="today" /></div>
</template>
ts
import { Component } from "@angular/core";
const calendarFixture = {
id: "calendar",
// Pinned, never `new Date()`: the demo has to render the same month, the
// same selected cell and the same today-ring in every framework, on every
// machine, forever. June 2026 also spans a full six-week grid with both
// leading and trailing spillover days, so the dimmed out-of-month cells are
// visible in the demo.
selectedIsoDay: "2026-06-18",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { GrassrootCalendar } from "@grassroot/ui-angular";
@Component({
selector: "app-calendar-demo",
standalone: true,
imports: [GrassrootCalendar],
template: `<div><grassroot-calendar [defaultValue]="selected" [today]="today" /></div>`,
})
export class CalendarDemoComponent {
protected readonly selected = isoDayToDate(calendarFixture.selectedIsoDay);
protected readonly today = isoDayToDate(calendarFixture.todayIsoDay);
}
export default CalendarDemoComponent;
ts
<script lang="ts">
const calendarFixture = {
id: "calendar",
// Pinned, never `new Date()`: the demo has to render the same month, the
// same selected cell and the same today-ring in every framework, on every
// machine, forever. June 2026 also spans a full six-week grid with both
// leading and trailing spillover days, so the dimmed out-of-month cells are
// visible in the demo.
selectedIsoDay: "2026-06-18",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { Calendar } from "@grassroot/ui-svelte";
</script>
<div>
<Calendar
defaultValue={isoDayToDate(calendarFixture.selectedIsoDay)}
today={isoDayToDate(calendarFixture.todayIsoDay)}
/>
</div>
ts
/** @jsxImportSource solid-js */
const calendarFixture = {
id: "calendar",
// Pinned, never `new Date()`: the demo has to render the same month, the
// same selected cell and the same today-ring in every framework, on every
// machine, forever. June 2026 also spans a full six-week grid with both
// leading and trailing spillover days, so the dimmed out-of-month cells are
// visible in the demo.
selectedIsoDay: "2026-06-18",
todayIsoDay: "2026-06-21",
} satisfies {
id: string;
selectedIsoDay: string;
todayIsoDay: string;
};
/**
* Turns a fixture's pinned `yyyy-mm-dd` string into the `Date` the calendar
* adapters take.
*
* Fixtures stay plain JSON (the proof test rejects `Date` instances, and
* `date` is a forbidden field name), but Calendar/DatePicker/DateRangePicker
* are `Date`-valued — so the conversion lives here, once, instead of five
* `new Date(...)` literals drifting apart in five demo files.
*
* Parsed field-by-field into *local* midnight on purpose: `new Date("2026-06-18")`
* is parsed as UTC midnight, which renders as the 17th everywhere west of
* Greenwich, so the demo month grid would highlight a different day depending
* on the reader's timezone. Field-by-field construction pins the rendered
* year/month/day in every timezone, forever.
*/
function isoDayToDate(isoDay: string): Date {
const [year, month, day] = isoDay.split("-").map(Number);
return new Date(year!, month! - 1, day!);
}
import { Calendar } from "@grassroot/ui-solid";
export default function CalendarDemo() {
return (
<div>
<Calendar
defaultValue={isoDayToDate(calendarFixture.selectedIsoDay)}
today={isoDayToDate(calendarFixture.todayIsoDay)}
/>
</div>
);
}

Installation

API Reference

Import

import { Calendar } from "@grassroot/ui-react";

Props

Prop
Type
Default
Requirement
Description
value
Date | null | undefined
optional
defaultValue
Date | null | undefined
optional
onValueChange
((date: Date) => void) | undefined
optional
today
Date | undefined
optional
Override "today" for the hairline ring (testing/stories).
Prop
value
Type
Date | null | undefined
Default
Requirement
optional
Description
Prop
defaultValue
Type
Date | null | undefined
Default
Requirement
optional
Description
Prop
onValueChange
Type
((date: Date) => void) | undefined
Default
Requirement
optional
Description
Prop
today
Type
Date | undefined
Default
Requirement
optional
Description
Override "today" for the hairline ring (testing/stories).

Events

EventPayload
onValueChange((date: Date) => void) | undefined

Native attributes

Standard DOM and ARIA attributes not listed above spread onto the root element.

Import

import { ref } from "vue";
import { Calendar } from "@grassroot/ui-vue";

Props

Prop
Type
Default
Requirement
modelValue
Date | null | undefined
optional
defaultValue
Date | null | undefined
optional
today
Date | undefined
optional
Prop
modelValue
Type
Date | null | undefined
Default
Requirement
optional
Prop
defaultValue
Type
Date | null | undefined
Default
Requirement
optional
Prop
today
Type
Date | undefined
Default
Requirement
optional

Events

EventPayload
update:modelValue[date: Date]

Import

import { Component } from "@angular/core";
import { GrassrootCalendar } from "@grassroot/ui-angular";

Props

Prop
Type
Default
Requirement
defaultValue
Date | null
null
optional
today
Date
optional
value
Date | null
null
optional
Prop
defaultValue
Type
Date | null
Default
null
Requirement
optional
Prop
today
Type
Date
Default
Requirement
optional
Prop
value
Type
Date | null
Default
null
Requirement
optional

Events

EventPayload
valueChangeDate | null

Import

import { Calendar } from "@grassroot/ui-svelte";

Props

Prop
Type
Default
Requirement
value
Date | null
optional
modelValue
Date | null
optional
defaultValue
Date | null
optional
today
Date
optional
className
string
optional
onValueChange
(date: Date) => void
optional
Prop
value
Type
Date | null
Default
Requirement
optional
Prop
modelValue
Type
Date | null
Default
Requirement
optional
Prop
defaultValue
Type
Date | null
Default
Requirement
optional
Prop
today
Type
Date
Default
Requirement
optional
Prop
className
Type
string
Default
Requirement
optional
Prop
onValueChange
Type
(date: Date) => void
Default
Requirement
optional

Events

EventPayload
onValueChange(date: Date) => void

Import

import { Calendar } from "@grassroot/ui-solid";

Props

Prop
Type
Default
Requirement
value
Date | null
optional
defaultValue
Date | null
optional
onValueChange
(date: Date) => void
optional
today
Date
optional
className
string
optional
Prop
value
Type
Date | null
Default
Requirement
optional
Prop
defaultValue
Type
Date | null
Default
Requirement
optional
Prop
onValueChange
Type
(date: Date) => void
Default
Requirement
optional
Prop
today
Type
Date
Default
Requirement
optional
Prop
className
Type
string
Default
Requirement
optional

Events

EventPayload
onValueChange(date: Date) => void