Demos
How to import
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
</script>
Day Mode
Day mode. You could toggle the red line of current time using showCurrTime.
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
</script>
<Calendar {displayValue} mode="day" />Week Mode
Week mode. You could toggle the red line of current time using showCurrTime.
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
</script>
<Calendar {displayValue} mode="week" />Month Mode
Month Mode.
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
</script>
<Calendar {displayValue} mode="month" />Set week start day
The day of the week can be set as the first day of the week through weekStartsOn, 0 for Sunday, 1 for Monday, and so on. Default is Sunday.
weekStartsOn takes effect for month view and week view.
Code
<script lang="ts">
import { Calendar, Radio, RadioGroup, type WeekStartsOn } from '@fibrae/semi-ui-svelte';
let weekStartsOn = $state(0);
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
</script>
<div>
<RadioGroup aria-label="周起始日" bind:value={weekStartsOn} name="demo-radio-group-vertical" type="button">
{#each days as day, index (day)}
<Radio value={index}>{day}</Radio>
{/each}
</RadioGroup>
<Calendar {displayValue} mode="month" style="margin-top: 20px" weekStartsOn={weekStartsOn as WeekStartsOn} />
</div>Range Mode
Range Mode. range is required which is a left-closed and right-open interval.
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
const range = [new Date(2020, 8, 26), new Date(2020, 8, 31)];
</script>
<Calendar mode="range" {range} />Render Events
You could pass in an array of event objects to events to render items. For detailed format, refer to Event Object below.
The content of an event defaults to the children text. For custom DOM use the event snippet; its { event } argument is the original object from events, which may carry any custom fields. When one event is shown in several places (such as a multi-day event), the snippet is called once for each place.
Code
<script lang="ts">
import { Calendar, DatePicker, Radio, RadioGroup, type CalendarMode, type EventObject } from '@fibrae/semi-ui-svelte';
let mode = $state('week');
let displayValue = $state(new Date(2019, 6, 23, 8, 32, 0));
const onChangeDate = (date: Date | Date[] | undefined) => {
if (date instanceof Date) displayValue = date;
};
const dailyEventStyle = 'border-radius: 3px; box-sizing: border-box; border: var(--semi-color-primary) 1px solid; padding: 10px; background-color: var(--semi-color-primary-light-default); height: 100%; overflow: hidden';
const allDayStyle = 'border-radius: 3px; box-sizing: border-box; border: var(--semi-color-bg-1) 1px solid; padding: 2px 4px; background-color: var(--semi-color-primary-light-active); height: 100%; overflow: hidden';
// `daily` marks the events that keep the padded style outside the month view; any extra key is passed to the event snippet.
const events: EventObject[] = [
{
key: '0',
start: new Date(2019, 5, 25, 14, 45, 0),
end: new Date(2019, 6, 26, 6, 18, 0),
children: '6月25日 14:45 ~ 7月26日 6:18',
},
{
key: '1',
start: new Date(2019, 6, 18, 10, 0, 0),
end: new Date(2019, 6, 30, 8, 0, 0),
children: '7月18日 10:00 ~ 7月30日 8:00',
},
{
key: '2',
start: new Date(2019, 6, 19, 20, 0, 0),
end: new Date(2019, 6, 23, 14, 0, 0),
children: '7月19日 20:00 ~ 7月23日 14:00',
},
{
key: '3',
start: new Date(2019, 6, 21, 6, 0, 0),
end: new Date(2019, 6, 25, 6, 0, 0),
children: '7月21日 6:00 ~ 7月25日 6:00',
},
{ key: '4', allDay: true, start: new Date(2019, 6, 22, 8, 0, 0), children: '7月22日 全天' },
{
key: '5',
start: new Date(2019, 6, 22, 9, 0, 0),
end: new Date(2019, 6, 23, 23, 0, 0),
children: '7月22日 9:00 ~ 7月23日 23:00',
},
{
key: '6',
daily: true,
start: new Date(2019, 6, 23, 8, 32, 0),
end: new Date(2019, 6, 23, 8, 42, 0),
children: '7月23日 8:32',
},
{
key: '7',
daily: true,
start: new Date(2019, 6, 23, 14, 30, 0),
end: new Date(2019, 6, 23, 20, 0, 0),
children: '7月23日 14:30-20:00',
},
{
key: '8',
start: new Date(2019, 6, 25, 8, 0, 0),
end: new Date(2019, 6, 27, 6, 0, 0),
children: '7月25日 8:00 ~ 7月27日 6:00',
},
{
key: '9',
start: new Date(2019, 6, 26, 10, 0, 0),
end: new Date(2019, 6, 27, 16, 0, 0),
children: '7月26日 10:00 ~ 7月27日 16:00',
},
];
const range = $derived(mode === 'range' ? [new Date(2019, 6, 23), new Date(2019, 6, 26)] : []);
</script>
<RadioGroup aria-label="视图模式" bind:value={mode} type="button">
<Radio value="day">日视图</Radio>
<Radio value="week">周视图</Radio>
<Radio value="month">月视图</Radio>
<Radio value="range">多日视图</Radio>
</RadioGroup>
<br />
<br />
<DatePicker onChange={onChangeDate} value={displayValue} />
<br />
<br />
<Calendar {displayValue} {events} height={400} minEventHeight={40} mode={mode as CalendarMode} {range}>
{#snippet event({ event })}
<div style={event.daily && mode !== 'month' ? dailyEventStyle : allDayStyle}>{event.children}</div>
{/snippet}
</Calendar>Custom Render
You could use the dateGrid snippet (argument { date, dateString }) to render customized date cell or column. Use absolute positioning for elements.
Custom Render Events
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
const dailyEventStyle = 'position: absolute; left: 0; right: 0; border-radius: 3px; box-sizing: border-box; border: var(--semi-color-primary) 1px solid; padding: 10px; background-color: var(--semi-color-primary-light-default); overflow: hidden';
const displayValue = new Date(2019, 6, 23, 8, 32, 0);
const targetDateString = new Date(2019, 6, 23).toString();
</script>
<Calendar {displayValue} height={700} mode="week">
{#snippet dateGrid({ dateString })}
{#if dateString === targetDateString}
<div style="{dailyEventStyle}; top: 500px; height: 50px">吃饭 🍰</div>
<div style="{dailyEventStyle}; top: 0; height: 400px">睡觉 😪</div>
<div style="{dailyEventStyle}; top: 700px; height: 100px">打豆豆 🎮</div>
{/if}
{/snippet}
</Calendar>Customized Date Cell Style
You could also use the dateGrid snippet to customize date cell style, e.g. backgroundColor. Please notice that in Month View, the date text on the right corner has a z-index of 3. Use a larger z-index if you would like to cover the text as well.
Code
<script lang="ts">
import { Calendar } from '@fibrae/semi-ui-svelte';
const importantDate = 'position: absolute; left: 0; right: 0; top: 0; bottom: 0; background-color: var(--semi-color-danger-light-default)';
const displayValue = new Date(2019, 6, 23, 8, 32, 0);
const importDates = [new Date(2019, 6, 2), new Date(2019, 6, 8), new Date(2019, 6, 19), new Date(2019, 6, 23)];
const importDateStrings = importDates.map((date) => date.toString());
</script>
<Calendar {displayValue} height={700} mode="month">
{#snippet dateGrid({ dateString })}
{#if importDateStrings.includes(dateString)}
<div style={importantDate}></div>
{/if}
{/snippet}
</Calendar>Customized Date Render
You could use the dateDisplay snippet (argument { date }) to customize the display of date.
Code
<script lang="ts">
import { Avatar, Calendar } from '@fibrae/semi-ui-svelte';
const displayValue = new Date(2023, 4, 14);
const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'lime'] as const;
</script>
<Calendar {displayValue} height={400} mode="week">
{#snippet dateDisplay({ date })}
<div style="padding-block: 6px">
<Avatar color={colors[date.getDay()]} size="small">{date.getDate()}</Avatar>
</div>
{/snippet}
</Calendar>API Reference
Calendar
Props
| Property | Description | Type | Default |
|---|---|---|---|
| allDayEvents | Snippet for the area for events at the top of the day, range and week views; events is the whole events array that was passed in, to be filtered by the snippet, which renders li items. | TypeSnippet<[CalendarAllDayEventsArgs]> | - |
| dateDisplay | Snippet for custom date text: the column titles of the week and range views and every cell of the month view. The day view does not use it. | TypeSnippet<[CalendarDateDisplayArgs]> | - |
| dateGrid | Snippet for custom content of a date cell or column, with the date and its dateString (date.toString()). Use absolute positioning for elements. | TypeSnippet<[CalendarDateGridArgs]> | - |
| displayValue | Display date. When omitted it is the current date and updates at midnight. | Date | - |
| event | Snippet for custom event content. event is the original object from events and the snippet is called once for each place the event appears. The text of children is rendered when it is absent. | TypeSnippet<[CalendarEventArgs]> | - |
| events | Events to render, see Event Object. | EventObject[] | [] |
| header | Snippet for custom header content. | Snippet | - |
| height | Height of the calendar; a number is in pixels. | number | string | 600 |
| markWeekend | Shows weekend columns in grey to tell them from weekdays. | boolean | false |
| minEventHeight | Minimum height in pixels of events in the day, range and week views. | number | - |
| mode | View mode: day, week, month or range. The view switches as soon as it changes. | CalendarMode | "week" |
| onClick | Called when a date cell is clicked, with the event and the date of the cell. The unit is half an hour in the day, week and range views and one day in the month view. | Type(event: MouseEvent, value: Date) => void | - |
| onClose | Called when the card showing all events of a day closes in the month view: the click event for the close button or a second click on "N more", the keyboard event for Esc, and null for a click outside. | Type(event: MouseEvent | KeyboardEvent | null) => void | - |
| onMoreClick | Called when a click on "N more" opens the card in the month view, with the event, the date and the number of collapsed events. It is not called when a second click closes the card. | Type(event: MouseEvent, date: Date, remaining: number) => void | - |
| range | Date range shown in range mode, left-closed and right-open. Required in range mode. | Date[] | - |
| scrollTop | Default scroll offset of the content in the day, week and range views. | number | 400 |
| showCurrTime | Shows the red line of the current time in the day, week and range views. | boolean | true |
| timeDisplay | Snippet for custom time text in the day, week and range views; time is the hour. | TypeSnippet<[CalendarTimeDisplayArgs]> | - |
| weekStartsOn | Day of the week that starts the week, 0 for Sunday, 1 for Monday and so on. Applies to the month and week views. | WeekStartsOn | 0 |
| width | Width of the calendar; a number is in pixels. | number | string | - |
class, style and other native attributes (such as id, data-*, aria-label) land on the root element of the current view; aria-label takes precedence over the default table name. onClick, onClose and onMoreClick are callbacks of Calendar itself and do not land on the root element.
Event Object
events is an array of event objects.
An event needs at least a start or an end time to be valid; an event with neither is skipped with a warning. An all-day event with only an end time starts on the day of its end. A timed event with only one end is extended by 1 hour, without crossing into another day. The end time is exclusive: a timed event that ends exactly at 00:00 is shown only on its start day, with no extra segment on the next day.
| Properties | Instructions | type | Default |
|---|---|---|---|
| allDay | Whether it is an all-day event | boolean | false |
| children | Displayed content, rendered as text without the event snippet | string | number | - |
| end | End time of the event | Date | - |
| key | Required and must be unique. | string | - |
| start | Start time of the event | Date | - |
| Other fields | Custom fields are kept as they are for the event snippet (color, title) | any | - |
Accessibility
Keyboard and Focus
- The day, week and range views scroll, and the view itself is one Tab stop that the arrow keys scroll
- In the month view, "N more" for the collapsed events is a button:
EnterorSpaceopens the card of the day and moves focus to its close button;Escor the close button closes the card and returns focus to "N more" - Clicks on a day cell or a time slot (
onClickin React,@clickin Vue,onClickin Svelte) respond to the pointer only and have no keyboard equivalent
Content Guidelines
- Both 12-hour and 24-hour clocks can be used when the time needs to be displayed
- If the 12-hour clock is used, it needs to be used together with AM/PM. For details, please refer to Time Specification
- For the abbreviation rules for month, week and time, please refer to Abbreviation Specification
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $color-calendar-bg-active | var(--semi-color-primary) | 今日标识颜色 |
| $color-calendar-bg | var(--semi-color-bg-2) | 日历背景颜色 |
| $color-calendar-text-active | var(--semi-color-bg-1) | 日历文字颜色 - 今日 |
| $color-calendar_curr-border | var(--semi-color-danger) | 当前时间标识线颜色 |
| $color-calendar_currCircle-bg-default | var(--semi-color-danger) | 当前时间标识原点颜色 |
| $color-calendar_date-text-default | var(--semi-color-text-0) | 日历文字颜色 - 当月 |
| $color-calendar_day-border | var(--semi-color-border) | 日历描边颜色 |
| $color-calendar_day-text-default | var(--semi-color-text-2) | 日历文字颜色 - 非当月 |
| $color-calendar_sticky-bg | var(--semi-color-bg-2) | 日历背景颜色 - 吸顶部分 |
| $color-calendar_weekend-bg | var(--semi-color-fill-0) | 日历背景颜色 - 非工作日 |
| $width-calendar_day-minWidth | 78px | |
| $height-calendar_day_grid | 60px | |
| $width-calendar_day_grid-minWidth | 130px | |
| $height-calendar_time_grid | $height-calendar_day_grid * 0.5 | |
| $height-calendar_allDay | 26px | |
| $height-calendar_allDay-minHeight | 26px | |
| $width-calendar_currCircle | 8px | |
| $width-calendar_tag_col-minWidth | 70px | |
| $height-calendar_month | 100% | |
| $height-calendar_month_week_skeletion | 100% | |
| $height-calendar_month_grid_wrapper | calc(100% - 27px) | |
| $width-calendar_card | 220px | |
| $width-calendar_today_date | 24px | |
| $height-calendar_today_date | 24px | |
| $height-calendar_body_li | 24px | |
| $height-calendar_day_scroll_wrapper | calc(100% - 28px) | |
| $height-calendar_week_scroll_wrapper | calc(100% - 55px) | |
| $height-calendar_month_skeletion_li | 100% | |
| $height-calendar_month_grid_col | 100% | |
| $height-calendar_month_week_row_event_month | 1em | |
| $width-calendar-border-default | 1px | |
| $width-calendar-border-thick | 1px | |
| $spacing-calendar_ul_li-padding | 0 | |
| $spacing-calendar_ul_li-margin | 0 | |
| $spacing-calendar_sticky_top-top | 0 | |
| $spacing-calendar_sticky_top-right | 0 | |
| $spacing-calendar_sticky_top-left | 0 | |
| $spacing-calendar_sticky_left-left | 0 | |
| $spacing-calendar_event_day-left | 0 | |
| $spacing-calendar_event_day-right | 0 | |
| $spacing-calendar_custom_container-top | 0 | |
| $spacing-calendar_custom_container-right | 0 | |
| $spacing-calendar_custom_container-bottom | 0 | |
| $spacing-calendar_custom_container-left | 0 | |
| $spacing-calendar_currCircle-marginTop | -4px | |
| $spacing-calendar_currLine-left | 0 | |
| $spacing-calendar_currLine-right | 0 | |
| $spacing-calendar_allDay_tag-paddingRight | 8px | |
| $spacing-calendar_allDay_content-paddingLeft | 0 | |
| $spacing-calendar_month_event_card_wrapper-paddingTop | 2px | |
| $spacing-calendar_grid_row_span_child-paddingY | 0 | |
| $spacing-calendar_grid_row_span_child-paddingX | 4px | |
| $spacing-calendar_skeleton_li_child-paddingTop | 4px | |
| $spacing-calendar_month_event_card_close-marginRight | -4px | |
| $spacing-calendar_header_info_date-marginTop | 4px | |
| $spacing-calendar_time-paddingRight | 8px | |
| $spacing-calendar_time_item_span-top | -10px | |
| $spacing-calendar_tag_child-paddingRight | 8px | |
| $spacing-calendar_skeletion_grid_row_li-paddingRight | 8px | |
| $spacing-calendar_month_event_card_wrapper-right | 8px | |
| $spacing-calendar_month_event_card_content-paddingY | 12px | |
| $spacing-calendar_month_event_card_content-paddingX | 0 | |
| $spacing-calendar_month_event_card_ul_li-margin | 0 | |
| $spacing-calendar_month_event_card_ul_li-padding | 0 | |
| $spacing-calendar_header-marginY | 12px | |
| $spacing-calendar_header-marginX | 20px | |
| $spacing-calendar_body-padingY | 0 | |
| $spacing-calendar_body-padingX | 16px | |
| $spacing-calendar_event_items-top | 31px | |
| $spacing-calendar_event_items-right | 0 | |
| $spacing-calendar_event_items-bottom | 20px | |
| $spacing-calendar_event_items-left | 0 | |
| $spacing-calendar_month_skeletion-left | 0 | |
| $spacing-calendar_month_skeletion-right | 0 | |
| $spacing-calendar_month_date-right | 4px | |
| $radius-calendar_currCircle | var(--semi-border-radius-circle) | 日历选中日期标识圆角 |
| $radius-calendar_today_date | var(--semi-border-radius-circle) | 日历选中日期标识圆角 |
| $font-calendar_allDay_tag-lineHeight | $height-calendar_allDay | 日历全天标识文字行高 |
| $font-calendar_today_date-lineHeight | 24px | |
| $font-calendar_day_allDay-lineHeight | 26px | |
| $font-calendar_day_allDay-fontSize | 26px | |
| $font-calendar_month_day-fontSize | 24px | |
| $z-calendar_stickyTop | 20 | |
| $z-calendar_item | 5 | |
| $z-calendar_stickyLeft | 10 | |
| $z-calendar_curr | 8 | |
| $z-calendar_line | 3 |