Updated: 2026-07-14 10:53 ET
Audited: 2026-07-14 10:53 ET
Icon is optional, not required — 2 of the 4 real usages (Documents List, both variants) render no icon at all.RenderFragment? ChildContent slot, not a fixed CtaLabel/OnCta
button pair — matches the already-established Expander.razor content-slot
precedent, and none of the 4 real consumers currently use a CTA at all, so a fixed button API would have been unvalidated
speculation.ALPAMobile.Presentation/Components/Library/ (the live Blazor component location since the 2026-07-15 WI-2254/D61 RCL migration, globally imported
via _Imports.razor), not ALPAMobile.HybridUi — that's the pre-development prototype codename
for the shared component-library RCL (used across the planning-era spec docs), not a created project; the RCL materializing
under that plan is ALPAMobile.Presentation per D20/WI-2254.ChildContent slot — arbitrary markup, not a fixed button compositionTier: scaffold/atom (per D40's own wording). Implemented in both namespace copies per the current dual-write convention — see Component Library Reconciliation Procedure / WI-2253 for the open question about whether that convention should continue.
Actual home: ALPAMobile.Presentation/Components/Library/EmptyState.razor (migrated 2026-07-15, WI-2254/D61; globally imported via _Imports.razor).
| Parameter | Type | Required | Description |
|---|---|---|---|
Vm | EmptyStateViewModel | Yes | Icon/Title/Subtitle state. |
Id | string? | No | Optional DOM id on the wrapper — preserves pre-existing UI-test automation hooks (e.g. Favorites' favorites.emptyState1). |
ChildContent | RenderFragment? | No | Optional CTA slot rendered below the subtitle. |
@if (Vm.IsEmpty)
{
<EmptyState Id="favorites.emptyState1"
Vm="@(new EmptyStateViewModel { Icon = "♡", Title = "No Favorites Yet",
Subtitle = "Bookmark flights, airlines, and resources for quick access" })" />
}
Classes live in ALPAMobile/wwwroot/css/alpa-components.css under the alpa-empty-state block
(renamed from the scaffold's Favorites-specific alpa-fav-empty*, which was shared vocabulary hand-copied
across 4 pages before this atom existed).
| Class | Scope | Key rules |
|---|---|---|
.alpa-empty-state | Container | display:flex; flex-direction:column; align-items:center; padding:64px 32px; text-align:center |
.alpa-empty-state-icon | Icon | font-size:48px; color:var(--alpa-gray); margin-bottom:16px; opacity:0.4 |
.alpa-empty-state-title | Title | font-family:var(--alpa-body); font-size:18px; font-weight:600; color:var(--alpa-navy); margin-bottom:8px |
.alpa-empty-state-sub | Subtitle | font-family:var(--alpa-body); font-size:13px; color:var(--alpa-gray); line-height:1.5 |
.alpa-empty-state-cta | Optional CTA | margin-top:16px — wraps arbitrary ChildContent |
| Screen | Icon | Title | Subtitle |
|---|---|---|---|
| Favorites — load failed | heart ♡ | Couldn't Load Favorites | Something went wrong reaching your favorites. Please try again later. |
| Favorites — empty | heart ♡ | No Favorites Yet | Bookmark flights, airlines, and resources for quick access |
| Saved Searches | search 🔍 | No Saved Searches | Save a search to find it quickly next time |
| Saved Flights | plane ✈ | No Saved Flights | Save flights from the results list to track them here |
| Documents List (2 usages) | none | No Documents | Nothing is published for this scope/category right now. |
FavoritesPage.razor (2 usages), JumpseatSavedFlightsPage.razor, JumpseatSavedSearchesPage.razor, DocumentsListPage.razor (2 usages).ComponentView.razor's dispatcher for consistency with every other atom, even though all 4 current consumers instantiate EmptyStateViewModel directly rather than through that dispatch path (same pattern as CardSmallViewModel in DocumentsListPage.razor) — kept open for future composed usage.