SGUI / Development
Calendar
One to three months with single/multiple selection and host availability explanations.
Loading examples…
Loading examples…
Examples
Loading examples…
Component documentation
SGUI / Development
One to three months with single/multiple selection and host availability explanations.
Loading examples…
Loading examples…
Loading examples…
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 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 |
Explore ISO values, field states and host-owned availability. The selected example theme also applies to picker portals.
Limits are inclusive and relative to today in your browser’s timezone, pinned when this example opens. Crossing a limit moves the other boundary to the same date.
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { Calendar } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function CalendarPlaygroundExample() {
const [value, setValue] = useState<string | null>("2026-10-14");
return (
<>
<ThemeScope density="comfortable">
<Stack align="start" gap={3}>
<Calendar calendarNavigation="independent" label={"Course schedule"} disabled={false} readOnly={false} unavailable={[]} navigation="period" allowMonthSelection={true} allowYearSelection={true} periodPickerProps={{"yearNavigation":"scroll","yearEntry":"select","yearsPerPage":12}} showTitle={false} selection="single" value={value} onValueChange={setValue} months={1} firstDayOfWeek="mon" defaultFocusedDate="2026-10-14" />
</Stack>
</ThemeScope>
</>
);
}
Compare segment, calendar and action target spacing in an independent scope.
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { Calendar } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function CalendarCompactExample() {
const [value, setValue] = useState<string | null>("2026-10-14");
return (
<>
<ThemeScope density="compact">
<Stack align="start" gap={3}>
<Calendar calendarNavigation="independent" label={"Course schedule"} disabled={false} readOnly={false} unavailable={[]} navigation="period" allowMonthSelection={true} allowYearSelection={true} periodPickerProps={{"yearNavigation":"scroll","yearEntry":"select","yearsPerPage":12}} showTitle={false} selection="single" value={value} onValueChange={setValue} months={1} firstDayOfWeek="mon" defaultFocusedDate="2026-10-14" />
</Stack>
</ThemeScope>
</>
);
}
Compare segment, calendar and action target spacing in an independent scope.
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { Calendar } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function CalendarComfortableExample() {
const [value, setValue] = useState<string | null>("2026-10-14");
return (
<>
<ThemeScope density="comfortable">
<Stack align="start" gap={3}>
<Calendar calendarNavigation="independent" label={"Course schedule"} disabled={false} readOnly={false} unavailable={[]} navigation="period" allowMonthSelection={true} allowYearSelection={true} periodPickerProps={{"yearNavigation":"scroll","yearEntry":"select","yearsPerPage":12}} showTitle={false} selection="single" value={value} onValueChange={setValue} months={1} firstDayOfWeek="mon" defaultFocusedDate="2026-10-14" />
</Stack>
</ThemeScope>
</>
);
}
Native fields serialize local values. Hosts own persistence, timezone and availability policy.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import { Calendar, Button } from '@structured-growth/sg-ui/experimental';
import { Stack, Typography } from '@structured-growth/sg-ui/primitives';
export default function CalendarFormExample() {
const [value, setValue] = useState<string | null>("2026-10-14");
const [submits, setSubmits] = useState(0);
return (
<>
<ThemeScope density="comfortable">
<form onSubmit={event => { event.preventDefault(); setSubmits(count => count + 1); }}>
<Stack align="start" gap={3}>
<Stack align="start" gap={3}>
<Calendar calendarNavigation="independent" label={"Course schedule"} disabled={false} readOnly={false} unavailable={[]} navigation="period" allowMonthSelection={true} allowYearSelection={true} periodPickerProps={{"yearNavigation":"scroll","yearEntry":"select","yearsPerPage":12}} showTitle={false} selection="single" value={value} onValueChange={setValue} months={1} firstDayOfWeek="mon" defaultFocusedDate="2026-10-14" />
</Stack>
<input type="hidden" name="schedule" value={JSON.stringify(value)} />
<Button type="submit">Submit schedule locally</Button>
<Typography role="status">Submitted {submits} times. Host owns persistence.</Typography>
</Stack>
</form>
</ThemeScope>
</>
);
}
Choose a departure date from today through two calendar months from today, inclusive. This demo uses your browser’s timezone and pins the window when opened. The host owns the booking timezone, availability and final booking validation. Month-end dates clamp to the last valid day.
'use client';
import { useState } from 'react';
import {useEffect} from 'react';
import {Calendar} from '@structured-growth/sg-ui/experimental';
export default function FlightBookingExample() {
function offsetCivilDate(iso:string,amount:number,unit:'days'|'months'|'years'):string {
const [year,month,day]=iso.split('-').map(Number);
const date=new Date(0);
date.setUTCFullYear(year,month-1,day);
if(unit==='days')date.setUTCDate(day+amount);
else {
date.setUTCDate(1);
if(unit==='months')date.setUTCMonth(month-1+amount);
else date.setUTCFullYear(year+amount);
const last=new Date(date.getTime());
last.setUTCMonth(last.getUTCMonth()+1,0);
date.setUTCDate(Math.min(day,last.getUTCDate()));
}
return date.toISOString().slice(0,10);
}
const [today,setToday]=useState<string|null>(null);
const [date,setDate]=useState<string|null>(null);
useEffect(()=>{
// Use the booking timezone's civil date in your application.
const now=new Date();
setToday(now.getFullYear()+'-'+String(now.getMonth()+1).padStart(2,'0')+'-'+String(now.getDate()).padStart(2,'0'));
},[]);
const latest=today?offsetCivilDate(today,2,'months'):undefined;
return (
<>
{today && <Calendar label="Departure date" min={today} max={latest} defaultFocusedDate={today} value={date} onValueChange={setDate}/>}
</>
);
}
Calendars fit seven tightly packed day targets by default, without a host width override. Compact pointer targets are 32px square; comfortable and touch targets are at least 44px. Six week rows are reserved as months change; narrow hosts scroll locally when necessary.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {Calendar} from '@structured-growth/sg-ui/experimental';
export default function compactCalendarExample() {
const [date,setDate]=useState<string|null>("2026-10-14");
return (
<>
<ThemeScope density="compact">
<Calendar label="Appointment" value={date} onValueChange={setDate} defaultFocusedDate="2026-10-14" />
</ThemeScope>
</>
);
}
Calendars fit seven tightly packed day targets by default, without a host width override. Compact pointer targets are 32px square; comfortable and touch targets are at least 44px. Six week rows are reserved as months change; narrow hosts scroll locally when necessary.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {Calendar} from '@structured-growth/sg-ui/experimental';
export default function comfortableCalendarExample() {
const [date,setDate]=useState<string|null>("2026-10-14");
return (
<>
<ThemeScope density="comfortable">
<Calendar label="Appointment" value={date} onValueChange={setDate} defaultFocusedDate="2026-10-14" />
</ThemeScope>
</>
);
}
Enter ISO dates, locale-ordered slash/dot dates, or a written month (for example Oct 14, 2026). Two-digit years expand within 1950–2049 (1/1/26 becomes January 1, 2026). Relative phrases such as today and in 2 weeks use the fixed host reference date 2026-10-16. Blur or Enter normalizes valid entry. Use the calendar button to choose a date instead. Impossible dates remain invalid; ambiguous numeric order follows the active locale.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DatePicker} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function CalendarDropdownExample() {
const [date,setDate]=useState<string|null>("2026-10-14");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'min(100%, 20rem)'}}>
<DatePicker label="Appointment date" entryMode="text" referenceDate="2026-10-16" value={date} onValueChange={setDate} defaultFocusedDate="2026-10-14" name="appointment" />
</Box>
</ThemeScope>
</>
);
}
The field-shaped trigger shows the committed range. Selected dates join into continuous weekly bands with rounded row ends. Initial selection completes in either direction. Once a range is complete, calendar clicks adjust the end by default; a click on or before the start restarts selection. This example enables optional freehand start/end textboxes above the calendar (hidden by default); type either endpoint or select an inclusive range. Apply commits and Cancel discards. The dropdown shifts within the viewport before it needs to scroll. Two months sit alongside each other when space permits and stack on narrow hosts. Formats normalize on blur or Enter; invalid, reversed or unavailable ranges cannot Apply.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DateRangePicker,type DateRange} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function RangeDropdown1Example() {
const [range,setRange]=useState<DateRange|null>({"start":"2026-10-12","end":"2026-10-16"});
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'min(100%, 20rem)'}}>
<DateRangePicker label="Travel dates" showFields entryMode="text" months={1} value={range} onValueChange={setRange} defaultFocusedDate="2026-10-12" name="travel" />
</Box>
</ThemeScope>
</>
);
}
The field-shaped trigger shows the committed range. Selected dates join into continuous weekly bands with rounded row ends. Initial selection completes in either direction. Once a range is complete, calendar clicks adjust the end by default; a click on or before the start restarts selection. This example enables optional freehand start/end textboxes above the calendar (hidden by default); type either endpoint or select an inclusive range. Apply commits and Cancel discards. The dropdown shifts within the viewport before it needs to scroll. Two months sit alongside each other when space permits and stack on narrow hosts. Formats normalize on blur or Enter; invalid, reversed or unavailable ranges cannot Apply.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DateRangePicker,type DateRange} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function RangeDropdown2Example() {
const [range,setRange]=useState<DateRange|null>({"start":"2026-10-12","end":"2026-10-16"});
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'min(100%, 20rem)'}}>
<DateRangePicker label="Travel dates" showFields entryMode="text" months={2} value={range} onValueChange={setRange} defaultFocusedDate="2026-10-12" name="travel" />
</Box>
</ThemeScope>
</>
);
}
Fixed and relative choices share one extensible list of selectable labels. This demo resolves full periods against October 16, 2026, with Monday-start weeks. The custom business-week option belongs to the host and can be omitted. A choice updates the draft; Apply commits and Cancel discards. Presets can precede or follow the calendars; visual and keyboard order match as panels wrap on narrow screens.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DateRangePicker,getRelativeDateRange,type DateRange,type DateRangePreset} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function RangePresets1Example() {
const [range,setRange]=useState<DateRange|null>({"start":"2026-10-12","end":"2026-10-18"});
// Resolve periods from the host's civil date, not the browser clock.
const referenceDate = '2026-10-16';
const lastWeek = getRelativeDateRange(referenceDate,{unit:'week',offset:-1,weekStartsOn:1});
// Example host policy: Monday-Friday, with no holiday-calendar adjustment.
const friday = new Date(lastWeek.start+'T00:00:00Z');
friday.setUTCDate(friday.getUTCDate()+4);
const presets:DateRangePreset[] = [
{id:'fixed',label:'Autumn launch',value:{start:'2026-09-01',end:'2026-09-30'}},
{id:'week',label:'This week',value:getRelativeDateRange(referenceDate,{unit:'week',weekStartsOn:1})},
{id:'month',label:'This month',value:getRelativeDateRange(referenceDate,{unit:'month'})},
{id:'year',label:'This year',value:getRelativeDateRange(referenceDate,{unit:'year'})},
{id:'last-year',label:'Last year',value:getRelativeDateRange(referenceDate,{unit:'year',offset:-1})},
{id:'business',label:'Last business week',description:'Custom host rule: Monday-Friday; holidays are not adjusted.',value:{start:lastWeek.start,end:friday.toISOString().slice(0,10)}},
];
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:"min(100%, 20rem)"}}>
<DateRangePicker label="Reporting period" entryMode="text" months={1} presetPlacement="side" presets={presets} firstDayOfWeek="mon" value={range} onValueChange={setRange} name="reporting" />
</Box>
</ThemeScope>
</>
);
}
Fixed and relative choices share one extensible list of selectable labels. This example explicitly enables the optional freehand endpoint textboxes. This demo resolves full periods against October 16, 2026, with Monday-start weeks. The custom business-week option belongs to the host and can be omitted. A choice updates the draft; Apply commits and Cancel discards. Presets can precede or follow the calendars; visual and keyboard order match as panels wrap on narrow screens.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DateRangePicker,getRelativeDateRange,type DateRange,type DateRangePreset} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function RangePresets2Example() {
const [range,setRange]=useState<DateRange|null>({"start":"2026-10-12","end":"2026-10-18"});
// Resolve periods from the host's civil date, not the browser clock.
const referenceDate = '2026-10-16';
const lastWeek = getRelativeDateRange(referenceDate,{unit:'week',offset:-1,weekStartsOn:1});
// Example host policy: Monday-Friday, with no holiday-calendar adjustment.
const friday = new Date(lastWeek.start+'T00:00:00Z');
friday.setUTCDate(friday.getUTCDate()+4);
const presets:DateRangePreset[] = [
{id:'fixed',label:'Autumn launch',value:{start:'2026-09-01',end:'2026-09-30'}},
{id:'week',label:'This week',value:getRelativeDateRange(referenceDate,{unit:'week',weekStartsOn:1})},
{id:'month',label:'This month',value:getRelativeDateRange(referenceDate,{unit:'month'})},
{id:'year',label:'This year',value:getRelativeDateRange(referenceDate,{unit:'year'})},
{id:'last-year',label:'Last year',value:getRelativeDateRange(referenceDate,{unit:'year',offset:-1})},
{id:'business',label:'Last business week',description:'Custom host rule: Monday-Friday; holidays are not adjusted.',value:{start:lastWeek.start,end:friday.toISOString().slice(0,10)}},
];
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:"min(100%, 20rem)"}}>
<DateRangePicker label="Reporting period" showFields entryMode="text" months={2} presetPlacement="side" presets={presets} firstDayOfWeek="mon" value={range} onValueChange={setRange} name="reporting" />
</Box>
</ThemeScope>
</>
);
}
Type a known birthday directly or open the optional calendar and jump by year and month. Navigation changes the view; choosing a day commits. This fixed demo permits 1900 through October 16, 2026; hosts choose their actual policy.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DatePicker} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function BirthdayPickerExample() {
const [date,setDate]=useState<string|null>(null);
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'min(100%, 22rem)'}}>
<DatePicker label="Select your birthday" entryMode="text" navigation="period" periodPickerProps={{yearsPerPage:24}} min="1900-01-01" max="2026-10-16" defaultFocusedDate="2000-01-01" name="birthday" description="For example, May 21, 1984. The host supplies the allowed dates." value={date} onValueChange={setDate}/>
</Box>
</ThemeScope>
</>
);
}
Edit date and time directly, or open the calendar and type a time such as 8a or use its clock dropdown. Apply commits both together; Cancel discards popup edits. The value is a local datetime without a timezone. The host resolves it to an instant and handles daylight-saving gaps or overlaps.
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {DatePicker} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function DateTimePicker24Example() {
const [value,setValue]=useState<string|null>("2026-10-14T09:30:00");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'min(100%, 28rem)'}}>
<DatePicker label="Appointment date and time" kind="datetime" hourCycle={24} navigation="period" min="2026-10-01T08:00:00" max="2026-10-31T18:00:00" name="appointment" description="Local appointment time in the host's chosen timezone." value={value} onValueChange={setValue}/>
</Box>
</ThemeScope>
</>
);
}
Three initially consecutive months share one selection. Month/year choices and arrows navigate only the calendar used; set calendarNavigation to linked to move all views together. Each month keeps its minimum width; months wrap in narrow hosts. The picker uses the same selector inside its dropdown.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 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 |
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
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 | 1 | 2 | 3 | 4 | 5 |
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
29 | 30 | 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 |
'use client';
import { useState } from 'react';
import { ThemeScope } from '@structured-growth/sg-ui/theme';
import {Calendar} from '@structured-growth/sg-ui/experimental';
import {Box} from '@structured-growth/sg-ui/primitives';
export default function ThreeMonthCalendarExample() {
const [date,setDate]=useState<string|null>("2026-10-14");
return (
<>
<ThemeScope density="compact">
<Box style={{inlineSize:'fit-content',maxInlineSize:'100%'}}>
<Calendar label="Quarter overview" months={3} value={date} onValueChange={setDate} />
</Box>
</ThemeScope>
</>
);
}