Inline calendar
The core inline month calendar with keyboard navigation and localization.
HTML
<script type="module">import '@nepali-date-picker/web-component';</script>
<nepali-date-picker value="2081-05-24" show-english></nepali-date-picker>
<script>
document.querySelector('nepali-date-picker')
.addEventListener('change', (e) => console.log(e.detail));
</script>
Bikram Sambat or Gregorian
Every calendar element can display either calendar. Only the display changes:
value, start, end and the change event
stay Bikram Sambat, so switching keeps the same day selected.
HTML
<!-- a switch the user can flip -->
<nepali-date-picker value="2083-06-01" show-calendar-toggle></nepali-date-picker>
<!-- or open straight on the Gregorian grid -->
<nepali-date-picker value="2083-06-01" calendar-system="ad"></nepali-date-picker>
<script>
// The payload is Bikram Sambat whichever calendar is on screen.
picker.addEventListener('change', (e) => console.log(e.detail.bsIso));
</script>
Neighbouring months in the empty cells
Fills the blank cells around the month with the days either side of it, drawn faded. Clicking one picks that day and moves the grid to its month. Off by default, so an existing grid is unchanged.
HTML
<nepali-date-picker value="2083-04-15" show-adjacent-month-days></nepali-date-picker>
Browsing a month
The read-a-month element rather than the pick-a-date one: both calendars' numbers and the
neighbouring months' days on by default, the picked day written out, and the month's
events listed with a span gathered into one line. An entry's id and
payload come back untouched, which is where an app keeps its own record,
image links included.
Click a day, or a line of the month's list.
HTML
<nepali-calendar
show-day-summary
show-month-events
weekly-off-days="7"
events='[{"date":"2083-06-10","days":4,"name":"Indra Jatra","kind":"religious",
"id":"indra-jatra","payload":"{\"imageUrl\":\"…\"}"}]'>
</nepali-calendar>
<script>
document.querySelector('nepali-calendar')
.addEventListener('event-select', (e) => {
// The payload is yours: parse it and render the image with your own <img>.
console.log(JSON.parse(e.detail.event.payload));
});
</script>
Both calendars in every cell
The displayed calendar's day stays large and its counterpart sits small in the corner of
the same cell, with the months the other calendar straddles named under the header. The
web twin of NepaliDatePickerWithEnglishDate in Compose. Off by default, and
the switch flips which calendar is the large one.
HTML
<nepali-date-picker value="2083-06-02" show-secondary-date></nepali-date-picker>
Dialog
A modal picker with confirm / cancel. Add fullscreen for the full-screen variant.
HTML
<button onclick="dlg.show()">Pick a date</button>
<nepali-date-picker-dialog id="dlg" value="2081-05-24"></nepali-date-picker-dialog>
<script>dlg.addEventListener('change', (e) => console.log(e.detail));</script>
Docked (field + popover)
Type a date or open the calendar in an anchored popover.
HTML
<nepali-date-picker-docked label="Appointment date"></nepali-date-picker-docked>
Range picker
Pick a start then an end; the span between is shaded.
HTML
<nepali-date-range-picker start="2081-05-10" end="2081-05-18"></nepali-date-range-picker>
<script>
document.querySelector('nepali-date-range-picker')
.addEventListener('change', (e) => console.log(e.detail.startBsIso, e.detail.endBsIso));
</script>
Text field
Type a date (YYYY/MM/DD, Devanagari digits accepted) with inline validation. No calendar, so it drops into forms.
HTML
<nepali-date-field label="Date of birth"></nepali-date-field>
<script>
field.addEventListener('change', (e) => console.log('valid', e.detail.bsIso));
field.addEventListener('invalid', (e) => console.log('invalid', e.detail.message));
</script>
Range text fields
Two validated fields with a start ≤ end check.
HTML
<nepali-date-range-field></nepali-date-range-field>
Wheel picker
Three scrolling columns; the day column resizes to the month. Scroll, click, or use the arrow keys.
HTML
<nepali-wheel-date-picker value="2081-05-24"></nepali-wheel-date-picker>
Marking days
A colour says what the day is; dots say what is scheduled on it. A weekly off day repeats fifty-two times a year, so it is coloured and never dotted, which leaves all three dot slots for an app's own events.
HTML
<nepali-date-picker
weekly-off-days="7"
events='[
{"date":"2081-05-26","name":"Constitution Day","kind":"governmentPublic"},
{"date":"2081-05-28","name":"Standup","indicate":true,"color":"#42a5f5"}
]'>
</nepali-date-picker>
An event that runs longer than a day
One entry, many days: give it endDate (included) or days and
every day of the span is marked the same way, across a month or a year end. A span is
capped at 366 days, and an end before the start marks the one day rather than
disappearing.
HTML
<nepali-date-picker
events='[
{"date":"2081-06-17","endDate":"2081-06-26","name":"Dashain","kind":"religious"},
{"date":"2081-07-02","days":5,"name":"Tihar","kind":"religious"}
]'>
</nepali-date-picker>
The same marking everywhere
events and weekly-off-days are attributes of every element
that draws a grid, so a docked field, a range picker and a dialog mark their days
identically. The colours come from CSS custom properties, so a brand palette is six
lines of CSS.
CSS
nepali-date-picker.brand-marks {
--ndp-weekly-off: #8e24aa;
--ndp-holiday-public: #d32f2f;
--ndp-holiday-religious: #1e88e5;
--ndp-holiday-regional: #00897b;
--ndp-holiday-observance: #6d4c41;
--ndp-holiday-container: rgba(211, 47, 47, 0.12);
}
Asking what a day is
The picker draws a colour and dots and no text. Everything else is a query on a policy: what the day is, what the month holds, and how many working days lie between two dates. Pick a day in the calendar to inspect it.
JavaScript
import {
createDetailedEvent, expandEventDays, createCalendarPolicy,
} from '@nepali-date-picker/core';
const dashain = createDetailedEvent(2081, 6, 17, 'Dashain', 'religious', true, 'dashain', null);
const office = createCalendarPolicy([7], expandEventDays(dashain, 10));
office.statusOf(2081, 6, 18).isNonWorking; // true
office.monthStatus(2081, 6).filter((d) => d.isNonWorking).length;
office.workingDaysBetween(2081, 6, 1, 2081, 6, 22); // end exclusive
office.nextWorkingDay(2081, 6, 17); // steps over the whole span
Bounds, disabled and validation
min and max are Bikram Sambat YYYY-MM-DD strings, and
every element that takes a date takes them: a day outside the window is drawn disabled
rather than hidden, and a typed one is rejected. disabled dims the whole
element and stops it responding. A field that cannot parse what is in it fires
invalid instead of change, and carries the reason.
HTML
<nepali-date-field label="Delivery date" min="2081-05-01" max="2081-07-30"></nepali-date-field>
<script>
field.addEventListener('change', (e) => console.log('valid', e.detail.bsIso));
field.addEventListener('invalid', (e) => console.log('rejected', e.detail.message));
</script>
Labels, headings and language
Every visible string is an attribute. A range field names its two sides separately with
start-label and end-label, a dialog takes a
heading, and language is per element, so one form can hold a
Nepali field next to an English one without a wrapper deciding for both.
HTML
<nepali-date-range-field start-label="Leave from" end-label="Leave until"></nepali-date-range-field>
<nepali-date-picker-dialog heading="When should we deliver?"></nepali-date-picker-dialog>
<nepali-date-field label="जन्म मिति" language="ne"></nepali-date-field>
Restyling a single element
The elements draw from custom properties rather than a stylesheet of their own, and
custom properties inherit through the shadow boundary. Setting them on one element
restyles just that one; setting them on :root restyles the page. Nothing
here needs a build step, a theme object, or a part selector.
CSS
.themed-picker {
--ndp-font: 'Iowan Old Style', Georgia, serif;
--ndp-bg: #12262a;
--ndp-text: #e7f5f4;
--ndp-muted: #7fa8a6;
--ndp-accent: #ffb703;
--ndp-on-accent: #12262a;
--ndp-hover: rgba(255, 183, 3, 0.16);
--ndp-in-range: rgba(255, 183, 3, 0.2);
--ndp-today-ring: #ffb703;
--ndp-border: #21454a;
--ndp-error: #ff8fa3;
--ndp-radius: 20px;
}
Conversion engine (no UI)
The same package powering the pickers, usable directly in Node or the browser with no DOM at all. Change either date below and every group recomputes.
JavaScript
import {
convertAdToBs, convertBsToAd, getTodayBs, getTodayAd, getCurrentTime,
getBsMonth, getAdMonth, getTotalDaysInBsMonth, getBsDaysBetween, compareBsDates,
getBsCalendarsInAdMonth, getAdCalendarsInBsMonth, isAdDateConvertible,
formatBsDate, formatBsDateByPattern, formatAdDateByPattern,
formatTimeEnglish, formatTimeNepali, bsDateTimeToIso, bsDateTimeFromIso,
localizeDigits, toLatinDigits, getBsYearRange, getAdYearRangeForBsYears,
} from '@nepali-date-picker/core';
convertAdToBs(2024, 9, 9); // { year: 2081, month: 5, dayOfMonth: 24, ... }
getBsMonth(2081, 5).totalDaysInMonth; // 31
formatBsDateByPattern('EEEE, dd MMMM yyyy', 2081, 5, 24, 'en');
bsDateTimeFromIso(bsDateTimeToIso(2081, 5, 24, 9, 30, 0, 0));
Use it in any framework
Custom elements are standard DOM, so every framework can render them.
import '@nepali-date-picker/web-component';
<nepali-date-picker value="2081-05-24"></nepali-date-picker>
import '@nepali-date-picker/web-component';
export function Picker() {
return (
<nepali-date-picker
value="2081-05-24"
onChange={(e) => console.log(e.nativeEvent.detail)}
/>
);
}
// React wraps the CustomEvent, so the payload is on nativeEvent.
// Import '@nepali-date-picker/web-component/react' once for TSX types.
// vite.config.js: mark the tag as a custom element
vue({ template: { compilerOptions: { isCustomElement: (t) => t === 'nepali-date-picker' } } })
<script setup>import '@nepali-date-picker/web-component';</script>
<template>
<nepali-date-picker value="2081-05-24" @change="(e) => console.log(e.detail)" />
</template>
// Add CUSTOM_ELEMENTS_SCHEMA to the module/component, then:
import '@nepali-date-picker/web-component';
<nepali-date-picker [attr.value]="value" (change)="onChange($event)"></nepali-date-picker>
<script>import '@nepali-date-picker/web-component';</script>
<!-- Svelte 5 -->
<nepali-date-picker value="2081-05-24" onchange={(e) => console.log(e.detail)} />
<!-- Svelte 4 -->
<nepali-date-picker value="2081-05-24" on:change={(e) => console.log(e.detail)} />
<!-- no bundler: one self-contained file, 64 kB gzipped -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@nepali-date-picker/web-component"></script>
<nepali-date-picker id="picker"></nepali-date-picker>
<script>
// jQuery needs no plugin; .val() does not reach a custom element property
$('#picker')[0].value = '2081-05-24';
$('#picker').on('change', (e) => console.log(e.detail.bsIso));
</script>