Language

NepaliCalendar

An interactive Bikram Sambat month calendar, with keyboard navigation and full styling control.

function NepaliCalendar(props: NepaliCalendarProps): JSX.Element

A month grid with previous/next buttons and month and year selects. Weeks run Sunday to Saturday, as Nepali calendars are laid out, and every value is a BSDate. All calendar math (month lengths, weekdays, navigation across years) comes from bikram-sambat-ts.

import { useState } from "react";
import { NepaliCalendar, type BSDate } from "bikram-sambat-react";
 
const [date, setDate] = useState<BSDate>();
 
<NepaliCalendar value={date} onChange={setDate} />;

Props

Every prop is optional.

Selection

PropTypeDefaultDescription
valueBSDate | undefined(uncontrolled)The selected date, controlled. Passing the prop at all, even as undefined, makes the selection controlled.
defaultValueBSDateThe initially selected date, when uncontrolled.
onChange(date: BSDate) => voidCalled when the user selects a day, including re-selecting the selected one.

Displayed month

PropTypeDefaultDescription
monthBSMonth(uncontrolled)The displayed month, controlled.
defaultMonthBSMonthsee the displayed monthThe initially displayed month, when uncontrolled.
onMonthChange(month: BSMonth) => voidCalled when the user navigates to another month.

Limits

PropTypeDefaultDescription
minDateBSDateBS 1979-01-01The first selectable date, inclusive.
maxDateBSDateBS 2100-12-31The last selectable date, inclusive.
isDateDisabled(date: BSDate) => booleanReturn true to disable a day. Disabled days can be focused but not selected.

Display

PropTypeDefaultDescription
locale"en" | "ne""en"Language of month names, weekday names and every label. See localization.
numerals"latin" | "devanagari"from localeDigits for day numbers and years: Devanagari for "ne", Latin otherwise.
todayBSDatethe device's dateThe day highlighted as today. See today.
showGregorianDatebooleanfalseShow each day's AD date under the BS day number.
dayShape"circle" | "rounded" | "square""circle"Shape of the day buttons (selection, today's ring, hover) with the default styles.
fixedWeeksbooleantrueAlways render six week rows, so the calendar's height never changes between months.
renderDay(date: BSDate, state: DayState) => ReactNodeCustom content inside a day button. See custom day content.

Styling

PropTypeDefaultDescription
classNamesPartial<CalendarClassNames>A class for each part, added to its default nc-* class.
componentsPartial<CalendarComponents>Replace the navigation buttons, arrows, selects or day buttons with your own components.
unstyledbooleanfalseLeave out the default nc-* classes, so the default stylesheet doesn't apply to this calendar.
classNamestringExtra class for the root element (the same as classNames.root).

All four are covered in styling & customization.

Other

PropTypeDefaultDescription
autoFocusbooleanfalseMove focus to the calendar's focusable day when it mounts.
idstringid of the root element.

Controlled and uncontrolled

NepaliCalendar follows React's usual value / defaultValue / onChange contract:

// Controlled: you own the state.
const [date, setDate] = useState<BSDate>();
<NepaliCalendar value={date} onChange={setDate} />;
 
// Uncontrolled: the calendar owns it; onChange only notifies you.
<NepaliCalendar defaultValue={{ year: 2083, month: 6, day: 15 }} onChange={(d) => console.log(d)} />;
  • Passing value at all makes it controlled, even when it's undefined. So useState<BSDate>() works as expected: undefined means nothing is selected, and the calendar never switches between controlled and uncontrolled.
  • A controlled calendar shows exactly value. If your onChange doesn't update it (to reject a selection, say), the selection doesn't change.
  • onChange fires on every selection, including clicking the day that's already selected. Pickers use this to close their popover.
  • Invalid dates are ignored, not thrown. A value, defaultValue, minDate, maxDate or today that isn't a real, supported BS date (e.g. { year: 2083, month: 2, day: 32 }) is treated as not given.

The displayed month

Without month or defaultMonth, the calendar starts on the first of these that applies:

  1. the month of value (or defaultValue)
  2. the month of today, if you pass it
  3. the current month in Nepal (bikram-sambat-ts's todayBs())

The result is kept within minDate and maxDate. When an uncontrolled month's calendar gets a new value from outside, in a month it isn't showing, it moves to that month.

To control the month, pass month and update it from onMonthChange. It's called for every kind of navigation: the buttons, the selects, and keyboard moves into another month.

import { NepaliCalendar, type BSMonth } from "bikram-sambat-react";
 
const [month, setMonth] = useState<BSMonth>({ year: 2083, month: 6 });
 
<NepaliCalendar month={month} onMonthChange={setMonth} />
<button onClick={() => setMonth({ year: 2083, month: 1 })}>Back to Baisakh</button>

Limiting dates

minDate and maxDate bound the selectable range; isDateDisabled disables any other day you choose. Here only Ashwin 5 to Kartik 10, 2083 can be picked, and never a Saturday:

import { getBsDayOfWeek } from "bikram-sambat-ts";
 
<NepaliCalendar
  defaultMonth={{ year: 2083, month: 6 }}
  minDate={{ year: 2083, month: 6, day: 5 }}
  maxDate={{ year: 2083, month: 7, day: 10 }}
  isDateDisabled={(date) => getBsDayOfWeek(date) === 6}
/>;
Live demobikram-sambat-react@0.1.0

Ashwin 2083

SunMonTueWedThuFriSat
value = undefined
  • Days outside the range, or rejected by isDateDisabled, can't be selected. They're marked with aria-disabled and a data-disabled attribute, and the default styles strike them through.
  • Keyboard navigation stops at minDate and maxDate, and the previous/next buttons, the year list and the month list only offer months inside the range.
  • Disabled days inside the range can still receive keyboard focus, so screen-reader users hear that they exist and are unavailable. This follows the WAI-ARIA date picker pattern.
  • Without minDate and maxDate, the range is everything bikram-sambat-ts supports: BS 1979-01-01 to 2100-12-31.
ⓘ

Tip: keep isDateDisabled fast and stable. It runs for every day each time the calendar renders. Define it outside the component, or wrap it in useCallback, if it closes over data.

Today

The calendar highlights today with a ring (and aria-current="date"). By default "today" is the device's local date, computed after hydration, so server-rendered and hydrated markup are identical. Calendars mounted later, like a date picker's popover, know today on their first render.

Pass today to choose the date yourself, for example today in Nepal, whatever the device's timezone:

import { todayBs } from "bikram-sambat-ts";
 
<NepaliCalendar today={todayBs()} />;

SSR & Next.js explains the details.

Gregorian dates

showGregorianDate adds each day's AD date in small text under the BS day number. It names the AD month on the 1st of that month and on the 1st of the BS month, so the column stays readable. The AD date is also added to each day's accessible label, e.g. Thursday, 15 Ashwin 2083 (1 October 2026 AD), and the calendar keeps exactly the same size with or without it.

<NepaliCalendar showGregorianDate defaultValue={{ year: 2083, month: 6, day: 15 }} />
<NepaliCalendar showGregorianDate locale="ne" defaultValue={{ year: 2083, month: 6, day: 15 }} />
Live demobikram-sambat-react@0.1.0

Ashwin 2083

SunMonTueWedThuFriSat

असोज २०८३

आइतसोममंगलबुधबिहिशुक्रशनि

Custom day content

renderDay replaces what's inside a day button. The button itself, its label, its state attributes and its keyboard behavior stay the calendar's, so accessibility is unaffected.

import { formatBsDate } from "bikram-sambat-ts";
 
// Example data: bikram-sambat-react doesn't ship holidays or events.
const events = new Set(["2083-06-03", "2083-06-11"]);
 
<NepaliCalendar
  renderDay={(date, state) => (
    <>
      {date.day}
      {events.has(formatBsDate(date)) && !state.selected && <span className="event-dot" />}
    </>
  )}
/>;

The second argument is the day's DayState: selected, today, disabled and focused. To change the button element itself, replace the DayButton component instead.

Layout

fixedWeeks (on by default) renders six week rows for every month, padding with empty rows, so the calendar never changes height as you navigate. It matters most inside a popover, which would otherwise jump. Ashwin 2083 needs five rows, for example; with fixedWeeks={false} it renders exactly those five.

dayShape switches the day buttons between circles (the default), rounded squares and squares:

Live demobikram-sambat-react@0.1.0
dayShape

Ashwin 2083

SunMonTueWedThuFriSat