Integration Guide

Practical recipes for embedding <lms-calendar> in different environments.

Vanilla JS / HTML

<!DOCTYPE html>
<html lang="en">
    <head>
        <script type="module" src="/node_modules/@jpahd/kalendus/dist/kalendus.js"></script>
        <style>
            lms-calendar {
                max-width: 1200px;
                margin: 0 auto;
            }
        </style>
    </head>
    <body>
        <lms-calendar id="demo"></lms-calendar>
        <script type="module">
            import events from './data/events.js';
            const calendar = document.getElementById('demo');
            calendar.entries = events;
            calendar.addEventListener('open-menu', (evt) => {
                console.log('Selected entry', evt.detail);
            });
        </script>
    </body>
</html>

Tips

React

Wrap the custom element using React.useEffect for imperative props:

import { useEffect, useRef } from 'react';
import '@jpahd/kalendus';

export function Calendar({ entries, onOpenMenu }) {
    const ref = useRef(null);

    useEffect(() => {
        if (ref.current) {
            ref.current.entries = entries;
            const handler = (event) => onOpenMenu?.(event.detail);
            ref.current.addEventListener('open-menu', handler);
            return () => ref.current.removeEventListener('open-menu', handler);
        }
    }, [entries, onOpenMenu]);

    return <lms-calendar ref={ref} color="#0d47a1" />;
}

Lit / Web Components

Because Kalendus is built with Lit, it interoperates directly:

import { LitElement, html } from 'lit';
import '@jpahd/kalendus';

export class HostCalendar extends LitElement {
    static properties = {
        entries: { type: Array },
    };

    render() {
        return html`
            <lms-calendar
                .entries=${this.entries}
                .heading="Library Schedule"
                year-density-mode="count"
            ></lms-calendar>
        `;
    }
}

Theming & CSS Tokens

Token Purpose
--primary-color Header buttons, selection highlights
--week-day-count / --week-mobile-day-count Number of columns in week view
--week-mobile-breakpoint Width threshold for condensed weeks
--entry-font-size, --entry-padding Entry card typography
--year-grid-columns* Year-view layout controls

Set tokens inline, via component-scoped styles, or globally.

lms-calendar.theme-ocean {
    --primary-color: #006994;
    --background-color: #022b3a;
    --entry-font-size: 0.85rem;
    --week-mobile-day-count: 4;
}

Analytics Hooks

Listen on the element for user actions:

Event Detail payload When it fires
switchview { view: 'day' | 'week' | 'month' | 'year' } Header view buttons
switchdate { direction: 'next' | 'previous' } Header navigation
peek-navigate { direction: 'next' | 'previous' } Condensed week peek arrows
expand { date, drillTarget } Month/Week/Year day clicks
open-menu { heading, content, time, date, anchorRect } Entry selection
locale-ready { locale: string } Locale chunk loaded
menu-close none Menu dismiss

Note: The expand event's drillTarget is only present when fired from the year view. See the Events Reference for full payload details.

Example (vanilla):

calendar.addEventListener('switchview', (event) => {
    analytics.track('calendar_switch_view', event.detail);
});

Connecting to the Kalendus API server

The server package (@jpahd/kalendus-server) exposes REST + SSE endpoints under /api/calendars. Use the shipped client wrappers to hydrate <lms-calendar> instances.

Vanilla client

import { KalendusApiClient } from '@jpahd/kalendus-server/client';

const client = new KalendusApiClient({
    baseUrl: 'https://api.example.com',
    calendarId: 'demo',
});

// Fetch a range and push into the component
client.fetchEvents('2026-03-01', '2026-03-31').then((entries) => {
    document.querySelector('lms-calendar').entries = entries;
});

// Subscribe to real-time changes
client.onSync((msg) => {
    console.log('SSE update', msg.type, msg.data);
});
client.connect();

Lit adapter

import { KalendusLitAdapter } from '@jpahd/kalendus-server/lit';

class RemoteCalendar extends LitElement {
    adapter = new KalendusLitAdapter(this, {
        baseUrl: 'https://api.example.com',
        calendarId: 'demo',
        enableSync: true,
        enableTelemetry: true,
    });

    render() {
        return html`
            <lms-calendar
                .entries=${this.adapter.entries}
                .heading="Synced Calendar"
            ></lms-calendar>
        `;
    }
}

The adapter listens to switchview/switchdate, computes the appropriate date window, fetches data, and (optionally) sends telemetry events back to the API.

Common Recipes

1. Toggle condensed weeks on phones

@media (max-width: 768px) {
    lms-calendar {
        --week-mobile-day-count: 3;
    }
}

2. Highlight weekends

The component uses Shadow DOM, so external CSS cannot target internal elements directly. Instead, use CSS custom properties to style day cells:

lms-calendar {
    --current-day-bg: #e3f2fd;
    --indicator-color: #1976d2;
}

For weekend-specific styling, listen to expand events and decorate surrounding UI, or apply conditional entry colors in your data layer.

3. Inject external actions into the menu

Use the public openMenu method:

calendar.openMenu({
    heading: 'Custom entry',
    content: 'Details rendered externally',
    time: { start: { hour: 8, minute: 0 }, end: { hour: 9, minute: 0 } },
    displayTime: '08:00 – 09:00',
});

4. Integrate with routing

calendar.addEventListener('expand', (event) => {
    const { date } = event.detail;
    router.push(`/schedule/${date.year}/${date.month}/${date.day}`);
});

5. Provide initial data from APIs

fetch('/api/events')
    .then((res) => res.json())
    .then((entries) => {
        calendar.entries = entries;
    });

FAQ

How do I lazy-load event data?

Listen to switchdate and expand events, then fetch data for the new date range before updating entries.

Can I render multiple calendars with different locales?

Yes—each instance holds its own ViewStateController. Set locale per component and they'll stay independent.

How do I theme the menu?

Override the menu CSS custom properties on the <lms-calendar> element (e.g., --menu-min-width, --menu-header-padding, --menu-title-font-size). See the CSS Token Reference for the full list.

Does Kalendus work with Shadow DOM encapsulation?

Yes. The component ships as a custom element with its own shadow root. Consumers can use standard custom-element patterns (attributes/properties/events) regardless of framework.

Further Reading