Series Note: This article is Part 6 of our hands-on engineering series: Building the Salesforce Appointment Scheduler. Follow along as we design and implement a multi-location scheduling platform on Salesforce using enterprise patterns and developer-first tooling.
Beyond Boilerplate in Lightning Web Components
Building an enterprise user experience in Salesforce often involves complex UI state management. When coordination staff handle customer calls, they cannot afford a disjointed experience of bouncing between list views, related lists, and modal popups.
They need a guided, cohesive multi-step wizard:
- Identify or register the customer.
- Select the service requested.
- Discover the nearest branch and choose an available staff slot.
- Review and transactionally confirm the booking.
Historically, building an LWC wizard in Salesforce meant writing mountains of XML configuration (js-meta.xml), managing target configurations (lightning__AppPage, lightning__Tab), creating nested folders, and stitching together child components.
In Part 6, we accelerate the frontend build using Hob: The Salesforce House-Elf (hob create lwc) to scaffold our component hierarchy, wire up reactive state machines, and package the solution into a dedicated Lightning App.
Scaffolding the Component Hierarchy with Hob
A clean frontend architecture relies on component composition: a parent orchestrator managing state, and focused child components managing individual wizard steps.
We scaffolded the orchestrator and four step components in seconds:
# Parent Orchestrator (Exposed to Lightning App Builder & Tabs)
hob create lwc appointmentWizard \
-l "Appointment Booking Wizard" \
-d "Multi-step guided appointment booking flow for call center operators" \
-t app tab
# Focused Step Components (Internal to the bundle)
hob create lwc customerSearchStep -l "Customer Search Step"
hob create lwc serviceSelectionStep -l "Service Selection Step"
hob create lwc slotPickerStep -l "Slot Picker Step"
hob create lwc bookingConfirmationStep -l "Booking Confirmation Step"
The Terminal Output
✨ Hob - The Salesforce House-Elf ✨
"Loyal, quiet, and tireless assistance for your Salesforce Development."
✔ Created LWC 'appointmentWizard'
🪄 Hob crafted your Lightning Web Component with target configurations:
Directory: force-app/main/default/lwc/appointmentWizard
Files: appointmentWizard.js, appointmentWizard.html, appointmentWizard.js-meta.xml
Targets: lightning__AppPage, lightning__Tab
Hob generates target tags automatically, eliminating the hassle of looking up Salesforce metadata strings. For internal child components, it sets isExposed = false, keeping the Lightning App Builder palette clean.
Architectural Blueprint: The Wizard State Machine
+-----------------------------------------------------------------------------------+
| appointmentWizard (Parent) |
| - Tracks wizardState: step (1..4), customer, service, location, slot, notes |
| - Renders persistent context header (Selected Customer, Service, Branch) |
| - Controls lightning-progress-indicator progression |
+-----------------------------------------------------------------------------------+
| | | |
v v v v
+--------------+ +--------------+ +--------------+ +--------------+
| Step 1 | | Step 2 | | Step 3 | | Step 4 |
| Customer | | Service | | Location and | | Confirmation |
| Search | | Selection | | Slot Picker | | & Recap |
+--------------+ +--------------+ +--------------+ +--------------+
Context Persistence
As operators progress, a sticky context bar displays selections made so far, keeping critical context visible:
<template if:true={hasContext}>
<div class="context-bar slds-m-bottom_medium slds-p-around_small slds-theme_shade slds-box slds-box_x-small">
<template if:true={selectedCustomer}>
<span class="context-pill">
<lightning-icon icon-name="standard:contact" size="x-small"></lightning-icon>
<strong>Customer:</strong> {selectedCustomer.Name} ({selectedCustomer.MailingPostalCode})
</span>
</template>
<template if:true={selectedService}>
<span class="context-pill">
<lightning-icon icon-name="standard:service_resource" size="x-small"></lightning-icon>
<strong>Service:</strong> {selectedService.Name} ({selectedService.Duration_Minutes__c} mins)
</span>
</template>
</div>
</template>
Step-by-Step Component Implementation
Step 1: Customer Search & Inline Creation (customerSearchStep)
Call center operators handle two scenarios: existing customers calling back, or first-time callers needing instant registration.
customerSearchStep handles both in a side-by-side card layout:
- Search Pane: Dispatches to
CustomerService.searchCustomersmatching on Name, Email, Phone, or Postal Code. Results display cards with click-to-select actions. - Quick Registration Form: Dispatches to
CustomerService.createCustomerto register a new customer in a single click without leaving the booking screen.
When a customer is selected or registered, the component fires a clean custom event:
this.dispatchEvent(new CustomEvent('customerselected', {
detail: customer,
bubbles: true,
composed: true
}));
Step 2: Service Selection (serviceSelectionStep)
Connects directly to AvailabilityService.getActiveServiceTypes to render the catalog of available appointment offerings:
- Responsive grid of selectable service cards (
slds-col slds-size_1-of-1 slds-medium-size_1-of-3). - Duration badges (
30 mins,60 mins,120 mins) using SLDS badge styling. - Hover elevation and active border states for clear visual feedback.
Step 3: Location Discovery & Slot Selection (slotPickerStep)
This step brings our Part 4 Apex engines to life in real time:
- Proximity-Ranked Branches:
- Immediately invokes
LocationService.findNearestLocationsusing the customer’s postal code coordinates. - Branches are ranked by distance (e.g.
2.4 km away), automatically highlighting the closest branch.
- Immediately invokes
- Shift-Aware Slot Picker:
- The operator chooses a booking date (defaults to tomorrow).
- Invokes
AvailabilityService.getAvailableSlots({ locationId, serviceTypeId, slotDate }). - Renders interactive slot pills displaying the start time and the scheduled specialist.
- Operator Booking Notes:
- Textarea for special customer requests or instructions.
Step 4: Summary Recap & Transactional Booking (bookingConfirmationStep)
Before writing to the database, the operator reviews all booking details in a structured card:
- Customer details and contact information.
- Selected branch location, address, and specialist.
- Date, start time, end time, and duration.
- Special notes.
When the operator clicks “Confirm & Book Appointment”:
- Invokes
AppointmentService.bookAppointmentasynchronously. - Under the hood, this triggers:
- Upon success, the UI swaps into a confirmation screen displaying the AutoNumber reference (
APT-0001) and a “Book Another Appointment” action to reset the wizard for the next caller.
Packaging the Experience: Custom Tabs & Lightning App
To make the wizard accessible to operators in Lightning Experience, we package the presentation layer:
1. Custom Lightning Component Tab (Appointment_Wizard.tab-meta.xml)
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Guided appointment booking wizard for service branches.</description>
<label>Book Appointment</label>
<lightningComponentBundle>appointmentWizard</lightningComponentBundle>
<motif>Custom67: Gears</motif>
</CustomTab>
2. Custom Application (Appointment_Scheduler.app-meta.xml)
A dedicated standard Lightning Application brings together the booking wizard and core entities into a unified workspace:
- App Name:
Appointment Scheduler - Navigational Tabs:
Appointment_Wizard(Default landing tab)Appointment__c(View all scheduled appointments)Service_Type__c(Manage service catalog)standard-Account(Branch locations)standard-Contact(Customers and staff)
3. Granting Operator Permissions
We update Appointment_Scheduler_Operator.permissionset-meta.xml to grant access to the application and tabs:
<applicationVisibilities>
<application>Appointment_Scheduler</application>
<visible>true</visible>
</applicationVisibilities>
<tabSettings>
<tab>Appointment_Wizard</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Appointment__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Service_Type__c</tab>
<visibility>Visible</visibility>
</tabSettings>
DX Verdict & Tool Feedback
What Shined
- Target-Aware Scaffolding:
hob create lwc <name> -t app tabeliminates the error-prone step of manually finding and typing the exactlightning__AppPageandlightning__TabXML strings. - Clean Component Defaults: Non-exposed child components are created instantly with
isExposed = false, keeping the Lightning App Builder palette uncluttered. - Rapid Iteration: Generating all five components took seconds, allowing immediate focus on state machine logic, CSS transitions, and Apex wiring.
Opportunities for Hob’s Backlog
- Scaffold Tabs from LWCs (
--with-tab):- When scaffolding an LWC with
-t tab, an optional flag--with-tabcould automatically create the companionforce-app/main/default/tabs/<Name>.tab-meta.xmlfile, eliminating another manual XML step.
- When scaffolding an LWC with
- Jest Test Generation (
--with-test):- Like
hob create apex --with-test, supportinghob create lwc <name> --with-testto scaffold__tests__/<name>.test.jswith standard@salesforce/sfdx-lwc-jeststubs would establish front-to-back TDD.
- Like
- Lightning App Scaffolding (
hob create app):- A command like
hob create app "Appointment Scheduler" --tabs Appointment_Wizard,Appointment__cwould complete the full metadata generation suite.
- A command like
The Appointment Scheduler Series
- Series Index: Building the Salesforce Appointment Scheduler: Architecture & Series Index
- ← Part 1: Eliminating Salesforce XML Fatigue — CLI Schema Scaffolding with Hob
- ← Part 2: Instant Least-Privilege Access — Generating Permission Sets with Hob
- ← Part 3: Standardizing Test Hygiene — In-Memory vs DML Factories with Hob
- ← Part 4: High-Velocity TDD — Companion Test Scaffolding with Hob
- ← Part 5: Architecture by Default — Separation of Concerns with Hob Triggers
- Part 6: Building Modern Lightning UIs Fast — Scaffolded LWCs with Hob (Current Article)
- Part 7: One Command to Rule Them All — The Complete Dev Org Setup with Hob →