Calendar
Basic interactive month calendar for date selection and lightweight date indicators. The free version is not a scheduler.
Overview
The Calendar component displays a month grid where users can select dates, navigate between months, view event indicators, inspect availability, or choose date ranges. It is a core building block for time-aware interfaces across the Byteion ecosystem.
Calendar is commonly used in booking dashboards, appointment scheduling, event management, ERP attendance, POS reservations, ecommerce delivery date selection, admin reporting date filters, and internal scheduling tools.
Calendar excels when users need spatial understanding of dates within a month. For simple one-field date input, the Date Picker may be a better fit.
What this component includes
- Month heading with year
- Previous / next month controls
- Weekday header row
- 7-column date grid
- Day buttons with select interaction
- Today state highlight
- Selected date highlight
- Outside-month date styling
- Disabled / unavailable date state
- Event indicator pills inside day cells
- Range selection states (start, middle, end)
- Optional multi-date selection
- Compact day-picker variant for popovers
- Custom day rendering via
renderDayprop (React only)
Use cases
When to use
Use Calendar when:
- users need to see dates in month context
- users need to choose from available / unavailable dates
- users need to view events or attendance by day
- users need range or multi-date selection
- users need to navigate month by month
When not to use
- users only need to type or pick one date quickly — use Date Picker
- users need a chronological event feed — use Timeline or List
- users need dense records — use Data Table
- users need time slots more than dates — use a scheduler or time picker if available
- mobile space is too limited and a simpler input suffices
Basic example
Month grid with navigation, weekday headers, selectable day buttons, today highlight, selected date, outside-month dates, disabled date, and an event indicator. All classes use Byteion semantic tokens.
June 2026
import { Calendar } from '@byteion/react';
export default function CalendarDemo() {
return (
<Calendar
month={5}
year={2026}
selected="2026-06-15"
events={[
{ date: '2026-06-15', label: 'Team standup', tone: 'primary' },
]}
/>
);
}
<script setup lang="ts">
import { Calendar } from '@byteion/vue';
</script>
<template>
<Calendar
:month="5"
:year="2026"
selected="2026-06-15"
:events="[{ date: '2026-06-15', label: 'Team standup', tone: 'primary' }]"
/>
</template>
Advanced example
Extended month grid showing today highlight, selected date, event dots, multiple event counts, unavailable date, and date range start / middle / end states.
June 2026
import { Calendar } from '@byteion/react';
export default function AdvancedCalendarDemo() {
return (
<Calendar
month={5}
year={2026}
selected="2026-06-15"
events={[
{ date: '2026-06-15', label: 'Team standup', tone: 'primary' },
{ date: '2026-06-20', label: 'Design review', tone: 'success' },
{ date: '2026-06-20', label: 'Sprint retro', tone: 'warning' },
]}
/>
);
}
<script setup lang="ts">
import { Calendar } from '@byteion/vue';
</script>
<template>
<Calendar
:month="5"
:year="2026"
selected="2026-06-15"
:events="[
{ date: '2026-06-15', label: 'Team standup', tone: 'primary' },
{ date: '2026-06-20', label: 'Design review', tone: 'success' },
{ date: '2026-06-20', label: 'Sprint retro', tone: 'warning' },
]"
/>
</template>
React example
Interactive React calendar with useState for current month, selected date, and event data. Uses the real <Calendar> component from @byteion/react.
import { useState } from 'react';
import { Calendar } from '@byteion/react';
const EVENTS = [
{ date: '2026-06-15', label: 'Team standup', tone: 'primary' as const },
{ date: '2026-06-26', label: 'Design review', tone: 'success' as const },
{ date: '2026-06-26', label: 'Sprint retro', tone: 'warning' as const },
];
export default function CalendarExample() {
const [month, setMonth] = useState(5); // June
const [year, setYear] = useState(2026);
const [selected, setSelected] = useState('2026-06-15');
return (
<Calendar
month={month}
year={year}
selected={selected}
events={EVENTS}
onMonthChange={(m, y) => { setMonth(m); setYear(y); }}
onSelect={(date) => setSelected(date)}
/>
);
}
Vue example
Interactive Vue 3 calendar using ref for current month, selected date, and events. Uses the real <Calendar> component from @byteion/vue.
<script setup lang="ts">
import { ref } from 'vue';
import { Calendar, type CalendarEvent } from '@byteion/vue';
const month = ref(5);
const year = ref(2026);
const selected = ref('2026-06-15');
const events = ref<CalendarEvent[]>([
{ date: '2026-06-15', label: 'Team standup', tone: 'primary' },
{ date: '2026-06-26', label: 'Design review', tone: 'success' },
{ date: '2026-06-26', label: 'Sprint retro', tone: 'warning' },
]);
function onMonthChange(m: number, y: number) {
month.value = m;
year.value = y;
}
function onSelect(date: string | null) {
selected.value = date;
}
</script>
<template>
<Calendar
v-model:month="month"
v-model:year="year"
v-model:selected="selected"
:events="events"
@monthChange="onMonthChange"
@select="onSelect"
/>
</template>
Anatomy
- Root container —
bg-cardwrapper withborder-borderand rounded corners. Contains the entire calendar surface. - Header — row with month/year label, previous/next controls, and optional Today button.
- Previous / next controls — icon buttons with
aria-labelfor navigating between months. - Month label — heading showing current month name and year, e.g. "June 2026".
- Weekday row — 7-column grid of abbreviated day names (Sun–Sat or Mon–Sun).
- Day grid — 7-column grid (typically 5–6 rows) containing day buttons.
- Day button — clickable cell showing the day number. Hover highlights with
hover:bg-muted. - Today state — day number rendered inside a filled circle using
bg-foreground text-background. - Selected state — day cell highlighted with
bg-primary-50/60and number in abg-primary-600circle. - Event indicator — pill or dot inside the day cell showing event labels.
- Disabled / unavailable state — muted styling with
opacity-50 cursor-not-allowed. - Outside-month date — dates from adjacent months shown with
bg-muted/40andtext-muted-foreground. - Optional footer — selected-date summary or event list below the grid.
Date states
Each day in the calendar grid can display one of the following states:
text-foreground.hover:bg-muted pointer feedback.focus-visible:ring-primary/20.bg-foreground text-background circle. Uses aria-current="date".bg-primary-50/60 cell, bg-primary-600 number circle. Uses aria-selected="true".bg-primary-50/60.disabled attribute and opacity-50 cursor-not-allowed.bg-muted/40 text-muted-foreground.Event indicators
The Calendar renders event indicators as pills inside day cells. Events are passed as an array of CalendarEvent objects with a date, label, and optional tone.
- Use a single pill when one event exists on a date.
- Stack multiple pills when multiple events exist on the same date.
- Choose
toneto communicate event type:primary,success,warning,danger,violet, orneutral. - On mobile, consider showing event details below the grid after a date is selected instead of inside cells.
- Do not rely on color alone — ensure event labels are readable text.
- Keep event colors token-based for theme consistency.
Selection modes
Single date selection
The default mode. One date is selected at a time. Use selected / onSelect (React) or v-model:selected (Vue). Best for picking a single appointment, deadline, or filter date.
Range selection
Two dates define a start and end. The Calendar highlights all dates between them. Implemented in your app by tracking a start and end date, then styling range days. Best for booking, hotel check-in/out, and report periods.
Multi-date selection
Multiple non-contiguous dates can be selected. Implemented in your app by storing an array of selected dates. Best for attendance marking, multi-day event selection, or shift scheduling.
Disabled / unavailable prevention
The Calendar prevents selection of disabled dates via the disabled attribute. Your app should also validate selected dates on the backend before processing bookings or reservations.
Clear selection
Clicking the same selected date again, or providing a clear/reset action, should clear the selection. The Calendar's onSelect fires null when called programmatically.
Selected-date summary
Display the selected date (or range) below the calendar for clarity. Common in booking flows where the user needs to confirm their choice before proceeding.
Month navigation
- Previous / next month buttons update the visible month. The Calendar handles this internally.
- An optional month/year picker can be added for jumping to a specific month faster.
- Preserve the selected date when possible — only clear it if the date no longer exists in the new month.
- Keep keyboard focus predictable after month change. Focus should return to the new month's grid or the selected date.
- Disable navigation buttons when min/max date limits are reached (e.g., cannot go before January 2020).
Accessibility
Calendar accessibility is more complex than basic buttons. Follow these guidelines:
- Use semantic
<button>elements for each date, or implement a proper ARIAgridpattern if arrow-key navigation is supported. - Provide visible focus states on all interactive elements.
- Previous / next buttons need accessible labels:
aria-label="Previous month"andaria-label="Next month". - The month heading (
<h3>) should be readable by screen readers without extra markup. - Day buttons need descriptive labels: e.g.
aria-label="Select June 15, 2026". - Today's date should use
aria-current="date". - The selected date should be announced — use
aria-selected="true"when using a grid pattern, or clear visible state when using plain buttons. - Disabled dates should use the
disabledattribute so they are skipped in tab order. - Event indicators need accessible text, not colour-only dots. Use the event label text.
- If arrow-key navigation is implemented: arrow keys move between days, Home/End to start/end of week, PageUp/PageDown to change months.
- Enter or Space should select the focused date.
- Keep focus predictable after month navigation — return focus to the same day number or the selected date.
Responsive behavior
- Calendar should fit narrow mobile screens — use a max-width container.
- On mobile, consider reducing day cell height (
min-h-[48px]instead ofmin-h-[96px]). - Avoid tiny hit targets — keep date buttons at least 44x44px touch-friendly.
- Show event details below the grid on mobile instead of inside cells.
- Avoid horizontal overflow — the 7-column grid naturally fits within 100% width.
- For complex scheduling on mobile, consider offering a list view alternative or a compact day-picker variant.
Date and timezone guidance
- For date-only selection, store normalized date strings such as
YYYY-MM-DD. - Avoid mixing date-only values with timezone-aware timestamps in the same field.
- Use one consistent date strategy across the UI — either all local dates or all UTC-based dates.
- Convert timezone-aware values at API boundaries, not in the calendar component.
- Test month boundaries, daylight saving changes, and users in different timezones.
- For booking and event systems, backend availability rules remain the source of truth — the UI is a reflection, not the authority.
Validation and constraints
Best practices
Do
- Use 6 rows when consistent calendar height matters.
- Show outside-month dates in muted style for spatial context.
- Use semantic token classes throughout.
- Provide clear visual distinction between today, selected, and default states.
- Keep event indicators readable — use text labels alongside colour.
- Disable unavailable dates to prevent invalid selections.
- Verify booking availability on the backend before confirmation.
- Test keyboard navigation and mobile touch behaviour.
Don't
- Use colour-only event indicators without accessible text.
- Make day cells too small to tap on mobile.
- Allow disabled dates to be selected via keyboard.
- Mix local and UTC dates without a clear strategy.
- Hide today or selected visual states.
- Use Calendar when a simple Date Picker is sufficient.
- Claim UI availability is final without backend verification.
Developer tips
Generate the month grid from a date utility function — the Calendar component handles this internally.
Keep selected date separate from the visible month. The user can navigate months without losing their selection.
Store date-only values as YYYY-MM-DD strings to avoid timezone shifting.
After changing months, consider returning focus to the first day of the new month or the selected date.
For booking systems, fetch availability data per visible month and treat the UI as a preview — always validate server-side.
For date ranges, style start and end dates differently from middle dates to clarify the selection.
In Laravel, validate selected dates server-side using date validation rules and custom availability checks.
In React and Vue, keep current month and selected date in component state or route query parameters if deep linking matters.
The Calendar supports a compact variant (set compact prop) for use inside popovers or Date Picker dropdowns.
The React Calendar provides a renderDay prop for fully custom day cell rendering. The Vue version does not expose this prop — compose with semantic HTML instead.
Related
- Date Picker — single-date input with dropdown calendar
- Input — text field for manual date entry
- Button — used in calendar navigation and Today action
- Badge — event indicator styling
- Popover — calendar dropdown container
- Card — calendar surface background
- Form Field — date selection within forms
- Alert — validation messages for date errors
- Timeline — chronological event feed alternative
- Spinner — loading state for availability
- Skeleton — placeholder for calendar loading
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
bi:calendar:month-changebi:calendar:select
React callbacks
onMonthChange(month: number, year: number) => voidonSelect(date: string | null) => void
Vue emits
update:selected[value: string | null]update:month[month: number]update:year[year: number]monthChange[month: number, year: number]select[value: string | null]
Remote / AJAX
This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced component patterns.
Explore Pro Components