Back to Widget Patterns
v2.6Interactive Tooling
Scheduling Tool
Calendly-style date and time picker with a contact form. Auto-generates default availability or accepts pre-defined slots. Three-step flow: date, time, contact details, confirmation.
Live preview
Rendered with real component code. Each example demonstrates a documented variant.
30-minute intro call
Book a 30-minute intro call
30 minute meeting
1. Pick a date
Custom slots plus a notes field
Schedule a strategy session
45 minute meeting
Pick a time and tell us what you would like to cover.
1. Pick a date
React props
Configure the component via props in React or via attributes in HTML.
| Prop | Type | Default | Description |
|---|---|---|---|
| onConfirm | (selection: BookingSelection) => Promise<void> | required | Called on submit with the selected date, time, duration, and form values; wire your booking backend here |
| title | string | undefined | Heading shown at the top of the scheduling widget |
| description | string | undefined | Supporting copy shown below the title |
| duration | number | 30 | Meeting length in minutes; shown as display text and passed to onConfirm |
| availableSlots | AvailableSlot[] | auto-generated | Explicit list of dates and times; omit to auto-generate the next 14 weekdays with standard morning and afternoon slots |
| formFields | FormField[] | name + email | Contact fields shown after a time is picked; supports text, email, tel, and textarea types |
| timezone | string | "Your local timezone" | Display label shown below the form to set timezone expectations |
| successText | string | "Booking confirmed" | Message shown after a successful onConfirm call |
| className | string | undefined | Extra class applied to the root section element |
HTML usage
The HTML variant uses the same shared styles and class names. Drop into any stack.
<link rel="stylesheet" href="path/to/primary-button-cta/styles.css">
<link rel="stylesheet" href="path/to/scheduling-tool/styles.css">
<section class="sch" id="booking-widget">
<h2 class="sch__title">Book a 30-minute intro call</h2>
<p class="sch__duration">30 minute meeting</p>
<p class="sch__step-label">1. Pick a date</p>
<div class="sch__dates" role="group" aria-label="Available dates">
<button class="sch__date" type="button" aria-pressed="false">
<span class="sch__date-weekday">Mon</span>
<span class="sch__date-day">19</span>
<span class="sch__date-month">May</span>
</button>
<!-- Repeat for each available date -->
</div>
<!-- Revealed after a date is selected -->
<p class="sch__step-label">2. Pick a time</p>
<div class="sch__times" role="group" aria-label="Available times">
<button class="sch__time" type="button" aria-pressed="false">9:00 AM</button>
<button class="sch__time" type="button" aria-pressed="false">9:30 AM</button>
</div>
<!-- Revealed after a time is selected -->
<form class="sch__form" id="booking-form">
<p class="sch__step-label">3. Your details</p>
<div class="sch__field">
<label class="sch__field-label" for="sch-name">Name <span aria-hidden="true"> *</span></label>
<input class="sch__field-input" id="sch-name" type="text" name="name" required>
</div>
<div class="sch__field">
<label class="sch__field-label" for="sch-email">Email <span aria-hidden="true"> *</span></label>
<input class="sch__field-input" id="sch-email" type="email" name="email" required>
</div>
<div class="sch__actions">
<button class="pbc pbc--solid pbc--rounded" type="submit">Confirm booking</button>
</div>
<p class="sch__timezone">Your local timezone</p>
</form>
</section>
<script>
(function () {
var dates = document.querySelectorAll('.sch__date');
var timesSection = document.querySelector('.sch__times');
var form = document.getElementById('booking-form');
var selectedDate = null;
var selectedTime = null;
dates.forEach(function (btn) {
btn.addEventListener('click', function () {
dates.forEach(function (b) { b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
selectedDate = btn.dataset.date;
timesSection.hidden = false;
});
});
document.querySelectorAll('.sch__time').forEach(function (btn) {
btn.addEventListener('click', function () {
document.querySelectorAll('.sch__time').forEach(function (b) { b.setAttribute('aria-pressed', 'false'); });
btn.setAttribute('aria-pressed', 'true');
selectedTime = btn.dataset.time;
form.hidden = false;
});
});
form.addEventListener('submit', function (e) {
e.preventDefault();
var submitBtn = form.querySelector('[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = 'Booking...';
fetch('/api/book', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ date: selectedDate, time: selectedTime }),
}).then(function () {
document.getElementById('booking-widget').innerHTML =
'<p class="sch__success-message">Booking confirmed</p>';
});
});
}());
</script>Customization
Override these CSS custom properties to integrate the component into your brand.
.sch {
--sch-bg: var(--brand-surface, white);
--sch-text-color: var(--brand-ink, #102542);
--sch-muted-color: rgba(16, 37, 66, 0.6);
--sch-accent: var(--brand-accent, #1e5fcf);
--sch-date-bg: white;
--sch-date-border: rgba(0, 0, 0, 0.1);
--sch-date-bg-selected: var(--sch-accent);
--sch-date-text-selected: white;
--sch-date-bg-hover: rgba(0, 0, 0, 0.025);
--sch-time-bg: white;
--sch-time-border: rgba(0, 0, 0, 0.1);
--sch-time-bg-selected: var(--sch-accent);
--sch-time-text-selected: white;
--sch-input-border: rgba(0, 0, 0, 0.12);
--sch-radius: 0.75rem;
--sch-section-padding: 2rem;
--sch-gap: 1rem;
}