🧭 LWC Zero to Hero — Module 8 of 15
Navigation, Modals & Toasts
The final piece of Phase 3 — moving users between pages, popping up dialogs, and giving clear feedback. These three tools complete a polished, professional LWC user experience.
Module 8 of 15 (counting Module 0 as a bonus prerequisite) · Phase 3: Component Communication (Final Module)
🎯 What You'll Master in This Module
Three independent but related tools round out how your component interacts with the rest of the application and the user. NavigationMixin moves the user to a different page entirely. The LightningModal API pops up a dialog without leaving the current page. ShowToastEvent gives quick, non-blocking feedback. Knowing exactly when each one is the right choice is as important as knowing their syntax.
✓ Why plain <a href> tags don't work well for Lightning navigation
✓ NavigationMixin and navigating to record pages
✓ Navigating to other page types — web pages, object pages, named pages
✓ The modern LightningModal API for popping up dialogs
✓ Building a custom modal component, including passing data in and out
✓ ShowToastEvent for success/error/warning/info user feedback
✓ A clear decision framework for navigation vs modal vs toast
📋 In This Module
Concept 1 of 7
NavigationMixin Fundamentals
A plain HTML
<a href="..."> tag causes a full browser page reload, which breaks the smooth, app-like feel of Lightning Experience and loses Salesforce's internal navigation context. NavigationMixin is a special "mixin" (a class that adds functionality to your component) that lets you navigate the Lightning way, without a full page reload.⚡ Why This Matters
Any time you need a button that takes the user to a different record, a different tab, or an external website, NavigationMixin is the correct, Salesforce-native tool — not a regular link.
EXTENDING NavigationMixin — the required setup
import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
export default class OrderCard extends NavigationMixin(LightningElement) {
// Notice: NavigationMixin wraps LightningElement, not the other way around
// This "mixin" pattern adds navigation methods to your class
handleViewOrder() {
this[NavigationMixin.Navigate]({
// page reference object goes here — covered in Concept 2
});
}
}
🛠️ Practical Mini-Implementation
The unusual-looking syntax
this[NavigationMixin.Navigate](...) (using square brackets instead of a normal dot) is required because NavigationMixin.Navigate is a special Symbol, not a plain string method name — this is just how the mixin pattern exposes its functionality. Treat it as a fixed syntax to memorize rather than something to deeply analyze.⚠️ Common Gotcha
Forgetting to wrap your class with
NavigationMixin(LightningElement) and instead just writing extends LightningElement means this[NavigationMixin.Navigate] won't exist on your component at all, causing a runtime error the moment you try to call it. The mixin must be applied at the class declaration itself.Concept 2 of 7
Navigating to Records
The most common navigation need is sending the user to a specific record's detail page. This uses a page reference object with
type: 'standard__recordPage', specifying the record's Id and which view (view, edit, or clone) to show.⚡ Why This Matters
"Click this row to open the full record" is one of the most universally needed UI patterns across every Salesforce app — mastering this one navigation type covers a huge percentage of real-world navigation needs.
NAVIGATING TO A RECORD'S DETAIL PAGE
import { LightningElement, api } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
export default class OrderCard extends NavigationMixin(LightningElement) {
@api recordId;
handleViewOrder() {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.recordId,
objectApiName: 'Order__c',
actionName: 'view' // could also be 'edit' or 'clone'
}
});
}
}
🛠️ Practical Mini-Implementation
Including
objectApiName alongside recordId is technically optional (Salesforce can often resolve the object type from the Id alone), but explicitly providing it is considered best practice — it avoids an extra server round-trip and makes your code's intent clearer to anyone reading it later.⚠️ Common Gotcha
Setting
actionName: 'edit' navigates directly into inline edit mode for the record — useful for "Edit" buttons, but easy to accidentally leave on a "View Details" button copy-pasted from elsewhere, resulting in users unexpectedly landing in edit mode when they only wanted to view the record.Concept 3 of 7
Other Page Types & generateUrl
Records aren't the only navigation target.
standard__webPage opens an external URL, standard__objectPage opens an object's list view (not a specific record), and standard__namedPage opens built-in pages like Home. generateUrl() gets the URL WITHOUT navigating — useful when you need the link itself (for a right-click "open in new tab," for example).⚡ Why This Matters
Interviewers often test whether you know the FULL set of page reference types, not just the record page one — real apps frequently need to link to external sites or object home pages too.
NAVIGATING TO AN EXTERNAL WEB PAGE
handleOpenDocs() {
this[NavigationMixin.Navigate]({
type: 'standard__webPage',
attributes: {
url: 'https://www.sfinterviewpro.com'
}
});
}
NAVIGATING TO AN OBJECT'S LIST VIEW
handleViewAllOrders() {
this[NavigationMixin.Navigate]({
type: 'standard__objectPage',
attributes: {
objectApiName: 'Order__c',
actionName: 'list'
}
});
}
GETTING A URL WITHOUT NAVIGATING — generateUrl()
async getOrderUrl() {
const url = await this[NavigationMixin.GenerateUrl]({
type: 'standard__recordPage',
attributes: { recordId: this.recordId, objectApiName: 'Order__c', actionName: 'view' }
});
return url; // can be used as an href, or copied to clipboard, without navigating away
}
🛠️ Practical Mini-Implementation
generateUrl() returns a Promise (notice the async/await from Module 1) — a common use case is setting that URL as a real href on an anchor tag in your template, so middle-click/right-click "open in new tab" works correctly, which a JavaScript-only Navigate call cannot support on its own.⚠️ Common Gotcha
The method name is
NavigationMixin.GenerateUrl (capital G), while the navigation one is NavigationMixin.Navigate (capital N) — both are correctly capitalized as shown, but it's easy to typo the capitalization when switching between the two, causing a silent "method does not exist" failure.Concept 4 of 7
The Modern LightningModal API
lightning/modal is Salesforce's official, modern API for popping up dialog boxes — replacing older patterns where developers built fully custom modal components from scratch using CSS positioning tricks. A modal component extends a special base class, LightningModal, and is opened via a static .open() method.⚡ Why This Matters
Before this official API existed, every team built their own modal pattern slightly differently, leading to inconsistent accessibility and behavior. The standardized API means modals behave predictably and accessibly out of the box.
OPENING A MODAL FROM ANY COMPONENT
import { LightningElement } from 'lwc';
import ConfirmDeleteModal from 'c/confirmDeleteModal';
export default class OrderList extends LightningElement {
async handleDeleteClick() {
const result = await ConfirmDeleteModal.open({
size: 'small',
description: 'Confirm deletion of this order'
});
if (result === 'confirmed') {
this.deleteOrder();
}
}
}
// .open() returns a Promise that resolves with whatever the modal passes back when closed
🛠️ Practical Mini-Implementation
Notice the modal is imported just like any other component (
import ConfirmDeleteModal from 'c/confirmDeleteModal'), but instead of being used in a template with a tag, it's called directly as ConfirmDeleteModal.open(...) — this is a fundamentally different usage pattern from every other component you've built so far in this course.⚠️ Common Gotcha
Forgetting
await before ConfirmDeleteModal.open(...) means your code continues executing immediately rather than waiting for the user to actually respond to the modal — leading to logic that runs before the user has made any choice at all. Since .open() returns a Promise, always await it (inside an async function, per Module 1's Concept 3).Concept 5 of 7
Building a Custom Modal Component
To build the modal itself, your component's JS class extends
LightningModal (instead of plain LightningElement), gaining access to special template slots for header/footer content and methods like this.close(result) to close the modal and pass data back to whoever opened it.⚡ Why This Matters
This is the "receiving end" of the pattern from Concept 4 — understanding both the open() call site and the modal's own implementation completes the full picture of how data flows in (via the open() options) and back out (via close()).
THE MODAL COMPONENT'S TEMPLATE — confirmDeleteModal.html
<template>
<lightning-modal-header label="Confirm Delete"></lightning-modal-header>
<lightning-modal-body>
<p>{description}</p>
</lightning-modal-body>
<lightning-modal-footer>
<lightning-button label="Cancel" onclick={handleCancel}></lightning-button>
<lightning-button variant="destructive" label="Delete" onclick={handleConfirm}></lightning-button>
</lightning-modal-footer>
</template>
THE MODAL COMPONENT'S CLASS — confirmDeleteModal.js
import { api } from 'lwc';
import LightningModal from 'lightning/modal';
export default class ConfirmDeleteModal extends LightningModal {
@api description; // receives the value passed via .open({ description: '...' })
handleCancel() {
this.close('cancelled'); // resolves the Promise from .open() with this value
}
handleConfirm() {
this.close('confirmed');
}
}
🛠️ Practical Mini-Implementation
Notice
@api description works exactly like the @api properties from Module 5 — the options object passed to .open({ description: '...' }) populates these properties on the modal instance, just like a parent setting an @api property on a child via HTML attributes, just with a different "calling" mechanism.⚠️ Common Gotcha
The special
<lightning-modal-header>, <lightning-modal-body>, and <lightning-modal-footer> tags are REQUIRED structural elements for a LightningModal component's template — using plain <div> tags instead breaks the modal's built-in styling, accessibility, and layout behavior.Concept 6 of 7
ShowToastEvent for User Feedback
A toast is a small, temporary, non-blocking notification — perfect for quick feedback like "Order saved successfully" without interrupting the user's flow the way a modal would.
ShowToastEvent is created and dispatched just like the custom events from Module 6, but it's a special built-in event type recognized by Salesforce's container.⚡ Why This Matters
Every save, delete, or background action in a real app needs SOME form of user feedback — toasts are the lightweight, standard way to confirm success or flag an error without forcing the user to dismiss a blocking dialog.
SHOWING A SUCCESS TOAST
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class OrderForm extends LightningElement {
handleSaveSuccess() {
const toastEvent = new ShowToastEvent({
title: 'Success',
message: 'Order saved successfully.',
variant: 'success' // success | error | warning | info
});
this.dispatchEvent(toastEvent);
}
handleSaveError(error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Error Saving Order',
message: error.body.message,
variant: 'error',
mode: 'sticky' // stays until user dismisses — good for errors needing attention
}));
}
}
🛠️ Practical Mini-Implementation
The
mode property controls dismissal behavior: 'dismissable' (default, auto-dismisses after a few seconds but can be closed early), 'pester' (auto-dismisses but reappears if the same error recurs), and 'sticky' (stays until manually dismissed). Use 'sticky' for errors the user genuinely needs to read and act on, not routine success messages.⚠️ Common Gotcha
ShowToastEvent only works inside Lightning Experience and the Salesforce mobile app — it does NOT work in Lightning Out, standalone Experience Cloud sites configured certain ways, or other contexts outside the standard Salesforce container. If toasts mysteriously don't appear, verify you're actually running inside a supported container.
Concept 7 of 7
Navigation vs Modal vs Toast — Decision Guide
Three different UX tools, three different jobs. Choosing the wrong one creates a confusing or annoying user experience even if the code works perfectly.
⚡ Why This Matters
Good LWC developers think about USER EXPERIENCE, not just "which API can technically do this" — interviewers testing for senior-level judgment often ask exactly this kind of "which would you choose and why" question.
THE DECISION FRAMEWORK
// NAVIGATION: user needs to fully move to different content
// "Take me to this Order's full record page"
// MODAL: user needs to focus on a quick task WITHOUT losing current context
// "Confirm this destructive action" / "Fill out this small form, then return here"
// TOAST: user needs brief confirmation/feedback, no decision required
// "Your changes were saved" / "Something went wrong, here's why"
🛠️ Practical Mini-Implementation
A common real combination: a Delete button opens a confirmation MODAL ("are you sure?"), and once confirmed, the actual deletion happens and a TOAST confirms "Order deleted successfully" — using all three communication-adjacent tools from this module working together in one realistic user flow.
⚠️ Common Gotcha — Phase 3 Wrap-Up
This module completes Phase 3 (Component Communication) — Modules 6 through 8 together cover every way components talk to each other AND to the user: events (parent-child), Pub-Sub/LMS (unrelated components), and now navigation/modals/toasts (user-facing feedback and flow control). Phase 4 shifts focus entirely to connecting components with actual Salesforce DATA via Apex and wire adapters.
💬 Module 8 Interview Questions (6)
Q1Why is using a plain HTML <a href="..."> tag generally discouraged for navigation within a Lightning Experience component, in favor of NavigationMixin?
A plain anchor tag triggers a full browser page reload, which breaks Lightning Experience's single-page application behavior, loses in-memory application state, and creates a jarring, non-native feeling transition compared to the smooth navigation Salesforce users expect. NavigationMixin uses Salesforce's internal navigation service to move between pages without a full reload, preserving the application's state and matching the navigation behavior of standard Salesforce pages and components.
"Plain anchor tags cause a full page reload that breaks Lightning Experience's app-like behavior and loses state, while NavigationMixin uses Salesforce's internal navigation service for smooth, native-feeling transitions without reloading the page."
Q2What is the purpose of including objectApiName when navigating to a record page with NavigationMixin, even though Salesforce can often resolve the object type from the recordId alone?
While Salesforce can technically resolve the object type from the Record Id format alone in many cases, explicitly providing objectApiName avoids an additional server-side lookup that would otherwise be needed to determine the object type, and it makes the code's intent immediately clear to anyone reading it later without needing to infer the target object from context. This is considered a best practice for both performance and code readability reasons, even when not strictly required for the navigation to function.
"Explicitly providing objectApiName avoids an extra server-side lookup to resolve the object type and makes the navigation code's intent clearer to future readers, even though Salesforce can sometimes infer the object type from the Record Id alone."
Q3How does data flow both into and out of a component built using the modern LightningModal API?
Data flows in through the options object passed to the static open() method when the modal is invoked — for example, open({ description: 'some text' }) — which populates corresponding @api properties declared on the modal component, working the same way standard @api properties receive values from a parent. Data flows back out when the modal calls this.close(result) internally, which resolves the Promise that the original open() call returned, allowing the calling component to read whatever value was passed to close() once the modal has finished and the user has made their choice.
"Data flows into the modal via the options object passed to open(), populating @api properties on the modal component, and flows back out when the modal calls this.close(result), which resolves the Promise that open() originally returned with that result value."
Q4A developer builds a custom LightningModal component using plain <div> tags instead of the special lightning-modal-header, lightning-modal-body, and lightning-modal-footer tags. What goes wrong, and why?
These special tags are required structural elements that the LightningModal base class and the underlying platform rely on for correct styling, layout positioning, and accessibility behavior such as proper focus management and screen reader support. Replacing them with plain div tags breaks this built-in structure, resulting in a modal that may render with broken or inconsistent styling, incorrect layout behavior, and degraded accessibility compared to a properly structured modal using the required template elements.
"The lightning-modal-header, lightning-modal-body, and lightning-modal-footer tags are required for correct styling, layout, and accessibility behavior in a LightningModal component; replacing them with plain div tags breaks this built-in structure and degrades the modal's appearance and accessibility."
Q5What is the difference between the 'dismissable', 'pester', and 'sticky' mode values for ShowToastEvent, and when would you use 'sticky' specifically?
'dismissable' is the default mode, where the toast automatically disappears after a few seconds but can also be manually closed early by the user. 'pester' mode also auto-dismisses but will reappear if the same underlying condition or error recurs, useful for recurring warnings the user might otherwise miss. 'sticky' mode keeps the toast visible until the user manually dismisses it, and is appropriate specifically for important error messages or critical information the user genuinely needs to read and act upon, rather than routine success confirmations that don't require sustained attention.
"'dismissable' auto-closes after a few seconds, 'pester' auto-closes but reappears if the same condition recurs, and 'sticky' stays until manually dismissed — 'sticky' should be reserved for important errors the user genuinely needs to read and act on, not routine success messages."
Q6A user clicks a "Delete" button on an Order record. Walk through how Navigation, a Modal, and a Toast might all be used together in this single user flow, and explain why each tool is the right choice at its specific point.
First, a Modal is the right choice for the initial confirmation step, since deletion is a destructive action requiring the user to make an explicit decision while keeping them anchored in their current context rather than navigating away. Once the user confirms within the modal, the actual deletion is performed, and a Toast is the appropriate tool to confirm the outcome, since this is brief, non-blocking feedback that doesn't require the user to make any further decision. Navigation would only enter this specific flow if, for example, deleting the current record required redirecting the user elsewhere afterward (such as back to a list view, since the record they were viewing no longer exists) — making Navigation appropriate for that specific "move to different content" need rather than for the confirmation or feedback steps themselves.
"A Modal handles the destructive-action confirmation since it requires a decision while preserving context, a Toast confirms the outcome since it's brief non-blocking feedback requiring no further decision, and Navigation would only apply if the user needed to be redirected elsewhere afterward, such as to a list view once their previous record no longer exists."
📝 Module 8 Recap — Navigation, Modals & Toasts Mastered
✅ NavigationMixin replaces plain <a href> tags for proper Lightning-native page navigation
✅ standard__recordPage navigates to records; standard__webPage, standard__objectPage handle other targets
✅ generateUrl() returns a URL without navigating — useful for real href attributes supporting "open in new tab"
✅ The modern LightningModal API uses a static .open() call and a Promise-based this.close(result) pattern
✅ Modal components require the special lightning-modal-header/body/footer structural tags
✅ ShowToastEvent gives brief, non-blocking feedback with success/error/warning/info variants and dismissable/pester/sticky modes
✅ Decision framework: Navigation moves to different content, Modal handles focused decisions in-context, Toast gives brief feedback
🎉 Phase 3 Complete — Component Communication Mastered!
Modules 6 through 8 covered every way components talk to each other and to the user: custom events for parent-child, Pub-Sub and Lightning Message Service for unrelated components and cross-framework communication, and Navigation/Modals/Toasts for user-facing flow control and feedback. Phase 4 shifts focus entirely to connecting your components with real Salesforce data — @wire adapters, imperative Apex calls, and advanced caching patterns.
🎯 Before Moving to Module 9...
Try this: build the full Delete flow from Q6 above — an Order list component where clicking Delete opens a custom confirmation modal (built using LightningModal), and upon confirmation, shows a success toast and navigates back to the Order object's list view. This single exercise combines all three tools from this module in one realistic, complete user flow. Module 9 begins Phase 4 — Data Integration — starting with @wire adapters, the declarative way to connect your components to real Salesforce records.
☕
☕ Enjoyed this article?
SF Interview Pro is 100% free and maintained by a Salesforce professional. No ads, no paywalls, and no signup required. If this guide helped you prepare for an interview, earn a certification, or grow your Salesforce career, consider buying me a coffee! ☕💜
🇮🇳 UPI (India)
Pay by QR
GPay · PhonePe · Paytm · BHIM
New interview questions every week 🚀
Follow for fresh Salesforce Q&A, free courses, and real interview experiences — straight from the trenches.
👥 Follow on LinkedIn