Embeddable Widget
Drop-in booking widget for any website with one script tag.
Embeddable Widget
@astrocal/widget is a drop-in booking UI for any website. It renders a full calendar, time slot picker, and booking form inside a Shadow DOM, so it works on any page without CSS conflicts.
Installation
bash npm install @astrocal/widget bash pnpm add @astrocal/widget bash yarn add @astrocal/widget <script src="https://cdn.astrocal.dev/widget/v1/astrocal.js"></script>Quick Start
Popup Mode
Open a booking modal with one call:
import { open } from "@astrocal/widget";
open({
eventTypeId: "YOUR-EVENT-TYPE-ID",
mode: "popup",
});Inline Mode
Mount the widget into a container element:
import { open, destroy } from "@astrocal/widget";
// Mount
open({
eventTypeId: "YOUR-EVENT-TYPE-ID",
mode: "inline",
target: "#booking-container",
});
// Clean up when done
destroy("#booking-container");CDN (No Build Step)
For sites without a bundler, load the widget via a script tag:
<script src="https://cdn.astrocal.dev/widget/v1/astrocal.js"></script>
<script>
Astrocal.open({
eventTypeId: "YOUR-EVENT-TYPE-ID",
mode: "popup",
});
</script>Or use auto-initialization with data attributes. No JavaScript required:
<script src="https://cdn.astrocal.dev/widget/v1/astrocal.js"></script>
<div data-astrocal-event-type-id="YOUR-EVENT-TYPE-ID" data-astrocal-mode="inline"></div>Configuration
| Property | Type | Default | Description |
|---|---|---|---|
eventTypeId | string | required | Event type UUID to display |
apiUrl | string | "https://api.astrocal.dev" | API base URL |
mode | "inline" | "popup" | "popup" | Render mode |
target | string | HTMLElement | - | DOM element or CSS selector (inline mode) |
timezone | string | auto-detected | IANA timezone override |
theme | ThemeConfig | - | CSS custom property overrides |
colorScheme | "light" | "dark" | "auto" | "auto" | Color scheme |
demo | boolean | false | Demo mode (mock data, no API calls) |
onBookingCreated | (booking: BookingResult) => void | - | Booking success callback |
onBookingRescheduled | (booking: BookingResult) => void | - | Reschedule success callback (reschedule mode) |
reschedule | { bookingId, token, currentStartTime? } | - | Reschedule an existing booking instead of creating one |
onError | (error: WidgetError) => void | - | Error callback |
onClose | () => void | - | Popup close callback |
API
open(config)
Opens the booking widget. In popup mode, creates a modal overlay. In inline mode, mounts into the target element.
close()
Closes the popup widget if open. No-op if no popup is active.
destroy(target)
Destroys an inline widget mounted on a target element. Removes the Shadow DOM and cleans up all resources.
autoInit()
Scans the DOM for elements with data-astrocal-* attributes and mounts widgets automatically. Called automatically when using the CDN script tag.
Theming
Customize the widget appearance with CSS custom properties:
open({
eventTypeId: "YOUR-EVENT-TYPE-ID",
theme: {
primaryColor: "#6366f1",
primaryHoverColor: "#4f46e5",
borderRadius: "12px",
fontFamily: "Inter, sans-serif",
},
});| Property | CSS Custom Property | Description |
|---|---|---|
primaryColor | --astrocal-primary | Primary action color |
primaryHoverColor | --astrocal-primary-hover | Primary hover color |
headingColor | --astrocal-heading | Heading text color |
backgroundColor | --astrocal-bg | Widget background |
textColor | --astrocal-text | Body text color |
borderColor | --astrocal-border | Border color |
borderFocusColor | --astrocal-border-focus | Focused border color |
borderRadius | --astrocal-radius | Border radius |
fontFamily | --astrocal-font | Font family |
The widget uses Shadow DOM isolation, so your site's styles won't affect it and vice versa.
Reschedule Mode
The widget can move an existing booking instead of creating a new one. Pass the booking's ID and its cancel token (both are in the booking response and in the confirmation email):
open({
eventTypeId: "YOUR-EVENT-TYPE-ID",
reschedule: {
bookingId: "BOOKING-ID",
token: "CANCEL-TOKEN",
currentStartTime: "2026-09-10T14:00:00.000Z", // optional, shows the old time
},
onBookingRescheduled: (booking) => console.log("moved to", booking.start_time),
});In reschedule mode the widget skips the duration selector and the details form. Picking a slot shows a confirm step with the current and new times and an optional reason, then calls POST /v1/bookings/{id}/reschedule?token=.... The onBookingCreated callback never fires in this mode.
Astrocal's own hosted pages at www.astrocal.dev/bookings/{id}/cancel and www.astrocal.dev/bookings/{id}/reschedule use this mode. Those are the links in every confirmation and reminder email, so you only need reschedule mode if you host your own booker experience.
Location and Meeting Links
The widget header shows where the meeting happens, taken from the event type's conferencing settings: "Zoom", "Google Meet", "Microsoft Teams", "Video call" for a custom link, or "In person" with the address. After booking, the confirmation screen shows a join link when the meeting was created, or the address for in-person events. If a video link is still being created (for example, a paid booking whose Zoom meeting is created after payment), the confirmation tells the booker the link will be in their email.
The custom meeting URL of an event type is never exposed before booking. GET /v1/public/event-types/{id} omits custom_meeting_url; the link arrives on the booking response as meeting_url.
SSR / Server-Side Rendering
The package is safe to import in Node.js (Next.js, Nuxt, etc.). The autoInit() function is guarded and will not run on the server. open() and destroy() require a browser environment. close() is a silent no-op on the server.
import { open } from "@astrocal/widget";
// Only call in browser context
if (typeof window !== "undefined") {
open({ eventTypeId: "..." });
}Using React? The @astrocal/react package wraps this widget with proper
lifecycle management. Mount, unmount, and prop updates are handled automatically.
Next Steps
- React SDK: React wrapper with provider and hooks
- Quickstart: REST API basics
- API Reference: Full endpoint documentation