Appearance
Availability Picker
A separate civil-date range picker for availability-aware selection. The component owns interaction and feedback; your application owns data access, persistence and pricing.
Vanilla JavaScript
ts
import { createAvailabilityPicker } from '@xsigns/chronosketch/availability'
import '@xsigns/chronosketch/availability/styles.css'
const picker = createAvailabilityPicker({
start: '2026-09-14',
days: 120,
months: 2,
locale: 'de-DE',
data: [
{ start: '2026-09-14', end: '2026-10-01', available: true, minimumDays: 3 },
{ start: '2026-10-01', end: '2026-10-05', available: false, canEnd: true },
{ start: '2026-10-05', end: '2027-01-12', available: true },
],
onSelectionChange: selection => {
// A complete valid { start, end, timeZone? }, or null after explicit reset.
// Store the value and request any final server validation in your app.
},
})
picker.mount(document.querySelector('#availability')!)
// On application disposal:
// picker.destroy()Dates and rules
All dates are YYYY-MM-DD civil dates; no midnight/timezone conversion occurs. Selection start is inclusive and end is exclusive. An unavailable end date can still close a range when canEnd permits it: its own availability is not consumed. The final selectable date is start + days - 1; no extra selectable end day is added. Boundary months are rendered in full, with padding dates not selectable.
Rules are nonoverlapping, half-open intervals. available is required; canStart and canEnd default to true, minimumDays to one. The selected duration must satisfy the maximum minimum across every included date, not only its start. reason is optional application text. Uncovered dates are unknown, never free. Even the exclusive end needs known end permission. Invalid/overlapping data throws. For boundary-only unavailability, supply a rule with available: false and canEnd: true. Optional timeZone is application context passed through unchanged.
First activation sets a start. Hover/focus previews an end; second activation confirms it. Invalid proposals show feedback without a nonempty callback. Arrow keys, Home/End and PageUp/PageDown move focus; Enter/Space activate. Shift plus PageUp/PageDown moves years. Touch activation gives the same validation feedback.
During an active selection, incoming data does not suddenly change its preview. Completion uses the latest data and explains a changed availability without emitting success. A completed selection remains visible after data changes and is marked as changed when no longer valid. Neither behavior reserves backend data.
Input structure and application flexibility
Each rule describes a continuous interval with identical rules; a separate object per calendar day is not required. Split intervals wherever a rule changes.
| Field | Required/default | Meaning |
|---|---|---|
start | Required | First included civil date, YYYY-MM-DD |
end | Required | Exclusive interval boundary |
available | Required | Whether the covered dates can be used |
canStart | true | Whether a selection can start on these dates |
canEnd | true | Whether a selection can end on these dates |
minimumDays | 1 | Minimum total selection duration when a covered date is included |
reason | Optional | Application-owned explanation, localized by the application |
The contract is domain-neutral: the application maps apartment bookings, maintenance closures, equipment rentals or other day-based use cases to these fields. A free date is not necessarily a valid start or end. Recurring rules (for example Saturday-only starts) must be resolved into nonoverlapping intervals by the application. Overlapping overrides are not merged automatically.
V1 validates these explicit rules; it does not expose an arbitrary custom validation callback. Pricing, capacity aggregation across multiple resources, provider-format conversion and final server checks belong to the application. Hourly slots are outside V1. Annotations/badges are separate presentation data and cannot grant or remove availability.
Updating and loading
setData(rules) replaces authoritative data and invalidates pending loads. setSelection(value | null) changes selection without echoing a callback. getSelection() reads it; checkSelection(range) returns an issue or undefined. update(options) accepts replacement options and preserves selection unless the selection value changes. Start/days are construction parameters; recreate the instance to change its horizon. reload() retries/refreshes the current query.
Alternatively supply provider: async (query, signal) => rules. The query has civil start and exclusive end, clipped to the visible months and horizon. Your application performs the request and propagates cancellation. Return nonoverlapping rules clipped to that query; they replace its previous coverage. An empty result means unknown coverage, not confirmed freedom. Throw on load failure.
The picker shows loading/error/retry states, cancels superseded requests and ignores late results. Successful windows are cached within the configured finite horizon. There is no persistent cache. Rule gaps inside a proposed selection block completion until supplied; applications may preload intermediate data with setData when supporting selections spanning months not visited by the user.
Presentation and content
months defaults to two and columns to two (responsive layout capacity). All configured months remain in the grid on narrow screens. interactive: false removes selection and navigation controls for a static overview.
showAvailable, showUnavailable, showRestrictions, showPreview and showAnnotations default to true. showGaps defaults to false and outlines dates on which at least one valid complete range can begin. It includes duration and boundary rules, not just raw freedom. Disabling a visual does not disable its rule. title overrides the component heading. labels can override reset, loading, failed, retry, choose and end reactively.
locale/locales use the existing localization contract, including week start and custom weekday/month names. Text changes preserve selection. text(context) can return your localized explanation or undefined for defaults. The context contains civil date, year/month/day, Sunday-zero weekday, ISO week and weekYear, rule, annotation, selected state and an optional issue. Issue codes are outside, unknown, unavailable, start, end, order, minimum, and changed; minimum issues include minimumDays.
renderDay(context, host) and renderTooltip(context, host) mount bounded custom content and may return a disposal function. Tooltip content appears in an accessible shared sticky hint card, usable with focus and touch. Never replace the date button itself. Text-only content should use textContent; the library never interprets application strings as HTML. The day area supports compact labels or application prices; prefer day badges for icons. No price model or price calculation is provided.
annotations accepts nonoverlapping { id, tag, start, end, label? } ranges. Compact bottom accents identify the range across week/month breaks. Labels and styling belong to the application; annotations never change availability. Use CSS tag selectors and the content hooks to specialize them. See styling.
React and Vue
React exports AvailabilityPicker from @xsigns/chronosketch/react; pass the same options as props. Replace data/selection objects when updating. Vue exports AvailabilityPicker from @xsigns/chronosketch/vue; pass the same object through its options prop. Both use the shared renderer and dispose mounted content. Import the availability CSS in all three integrations. DOM content hooks can mount framework content when their returned cleanup unmounts it.
Swift
See native Availability Picker. Web and Swift share interval rules and ownership, with separate platform-native presentation. Hourly selection is not part of this version.
Available-day shape
Web dayStyle: 'squared' | 'rounded' and Swift configuration.dayStyle (.squared / .rounded) control the available-day surface. Squared is the default. The surface is centered at 36px/pt maximum (Web shrinks it further in narrow cells), with small square corners or a circle. The month grid and minimum 44px/pt hit area stay unchanged. Restriction, annotation and bookable-gap indicators fit the same compact day surface. Selection and preview retain their range markings. Both demos expose this setting as “Day shape”.
Day badges
dayBadge(context) returns one AvailabilityDayBadge or undefined. The badge sits slightly outside the upper-right edge of the day surface, preserving the day number, grid and hit area. It is decorative, with no separate click action.
ts
dayBadge: context => context.annotation ? {
color: '#237a42',
iconColor: '#ffffff',
label: 'Special offer', // Localize in the application.
content: { type: 'icon', icon: 'material:check' },
} : undefinedcolor is required; iconColor defaults to white and also colors numbers. Omit content (or use { type: 'dot' }) for an empty filled circle. Use { type: 'number', value: 3 } for a count or a catalog icon as above, in a 16px badge. Counts must be nonnegative safe integers; zero is visible and counts above 99 show 99+. The required accessible label should include the full count. Every badge has a fixed 16px diameter without a border or outline. Text is never interpreted as HTML.
The callback receives the same context as other content hooks and can update reactively. Return undefined to remove the badge. Badges do not affect rules or require annotations; showAnnotations does not automatically hide them. Annotation labels now remain metadata instead of being rendered inside the day. Move inline icons to dayBadge; existing custom day/tooltip hooks remain supported.
Indicator color
indicatorColor (Swift: configuration.indicatorColor) sets period-annotation accents and restriction indicators on available days. Supply a CSS color on Web or SwiftUI Color on iOS; the default follows the accent color. For example, use indicatorColor: 'var(--xc-accent)' or configuration.indicatorColor = .accentColor. Bookable-gap outlines use a neutral border to distinguish them from the indicator. Unavailable-day styling, badge colors and selection colors remain independent. Changing this presentation option preserves selection and does not affect rules.
Badge precedence over annotation accents
A day with a badge suppresses its additional bottom annotation accent. This avoids a duplicate visual marker alongside the badge and bookable-gap outline. Removing the badge restores the annotation accent when annotations are enabled. Rule restriction indicators, gap outlines and selection markings remain independent. This presentation precedence applies on Web and iOS and changes no availability or annotation data.
Badge sizing refinement: dot, icon and number share a fixed 16px/pt circle. No border, outline or outline-like shadow is drawn. Numeric content uses 7px/pt text so 99+ fits without changing badge dimensions.
Selection shape refinement: squared day styling uses a small 4px/pt selection corner radius. Web start/end round only the outer corners and retain straight inner connections. Rounded mode retains its existing selection geometry. Switching shape preserves the selected dates and does not affect selection rules.
Shared picker frame — 2026-09-15
All visible months belong to one shared card with common navigation and selection feedback/actions. Month headings use a subtle surface band and separator; months are separated by fine rules, not individually elevated cards. Side-by-side months have vertical separation; stacked rows have horizontal separation.
The card follows the configured column capacity (up to 360px/pt per column, capped by visible-month count and available width). Narrow layouts retain all months and stack them. Web removes the unused reserved detail height; actual feedback retains its accessible live region. There is no new model option or change to selection, loading, badges, date semantics or static-mode rules. Existing shape settings continue to control days and selection endpoints independently of the outer card.
Shared hint card
showHints defaults to true. Interactive pickers show one reserved bottom hint card with a leading accent indicator, shared across all months. Pointer/focus feedback on Web and the current touched/hovered day on iOS update this card. Disable it independently of selection previews; static mode has no hint card. Web uses a sticky footer bounded by the picker; native uses a bottom safe-area inset. The card reserves 72px/pt (native scales with Dynamic Type), with internal scrolling for longer application content. text and existing custom tooltip content supply its contents. Selection errors and loading feedback remain visible independently of this setting. Both demo settings expose Hint card.
This replaces the former per-month Web detail area and supersedes the earlier empty-detail collapse behavior. No data or selection contract changes.
The confirmed selection appears inside the hint card above the changing day hint. Hover/focus updates never replace that selection summary. With hints disabled, the summary remains in the ordinary footer/feedback area. The Web demo no longer duplicates raw ISO selection dates below the picker.