Chapter 17: UI Component Library
The Shared Design System
This chapter defines the complete UI component library shared across all POS Platform applications. These specifications ensure visual consistency, reduce development time, and enable rapid prototyping.
17.1 Design Tokens
Color Palette
Primary Colors
| Token | Hex | RGB | Usage |
|---|---|---|---|
--color-primary | #1976D2 | 25, 118, 210 | Main brand, primary buttons, links |
--color-primary-dark | #1565C0 | 21, 101, 192 | Hover states, headers |
--color-primary-light | #BBDEFB | 187, 222, 251 | Selected backgrounds, info panels |
--color-primary-50 | #E3F2FD | 227, 242, 253 | Subtle backgrounds |
Secondary Colors
| Token | Hex | RGB | Usage |
|---|---|---|---|
--color-secondary | #424242 | 66, 66, 66 | Secondary buttons, icons |
--color-secondary-dark | #212121 | 33, 33, 33 | Text, headings |
--color-secondary-light | #757575 | 117, 117, 117 | Secondary text, labels |
Status Colors
| Token | Hex | RGB | Usage |
|---|---|---|---|
--color-success | #4CAF50 | 76, 175, 80 | Success states, positive |
--color-success-light | #E8F5E9 | 232, 245, 233 | Success backgrounds |
--color-success-dark | #2E7D32 | 46, 125, 50 | Success text on light bg |
--color-warning | #FF9800 | 255, 152, 0 | Warning states, caution |
--color-warning-light | #FFF3E0 | 255, 243, 224 | Warning backgrounds |
--color-warning-dark | #E65100 | 230, 81, 0 | Warning text on light bg |
--color-error | #F44336 | 244, 67, 54 | Error states, destructive |
--color-error-light | #FFEBEE | 255, 235, 238 | Error backgrounds |
--color-error-dark | #C62828 | 198, 40, 40 | Error text on light bg |
--color-info | #2196F3 | 33, 150, 243 | Informational states |
--color-info-light | #E3F2FD | 227, 242, 253 | Info backgrounds |
--color-info-dark | #1565C0 | 21, 101, 192 | Info text on light bg |
Neutral Colors
| Token | Hex | Usage |
|---|---|---|
--color-white | #FFFFFF | Card backgrounds, content areas |
--color-gray-50 | #FAFAFA | Alternating row backgrounds |
--color-gray-100 | #F5F5F5 | Page backgrounds, disabled |
--color-gray-200 | #EEEEEE | Light borders, dividers |
--color-gray-300 | #E0E0E0 | Standard borders |
--color-gray-400 | #BDBDBD | Input borders, icons |
--color-gray-500 | #9E9E9E | Disabled text, placeholders |
--color-gray-600 | #757575 | Secondary text |
--color-gray-700 | #616161 | Icons, labels |
--color-gray-800 | #424242 | Body text |
--color-gray-900 | #212121 | Headings, primary text |
--color-black | #000000 | Maximum contrast |
Typography Scale
Font Families
--font-family-base: 'Segoe UI', -apple-system, BlinkMacSystemFont,
'Roboto', 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'Cascadia Code', 'Fira Code', 'Consolas',
'Monaco', 'Courier New', monospace;
Font Sizes
| Token | Size | Line Height | Usage |
|---|---|---|---|
--font-size-xs | 11px | 1.4 | Captions, badges |
--font-size-sm | 12px | 1.4 | Secondary text, timestamps |
--font-size-base | 14px | 1.5 | Body text, inputs |
--font-size-md | 16px | 1.5 | Emphasized body |
--font-size-lg | 18px | 1.4 | Section headers |
--font-size-xl | 20px | 1.3 | Card titles |
--font-size-2xl | 24px | 1.3 | Page titles |
--font-size-3xl | 30px | 1.2 | Dashboard stats |
--font-size-4xl | 36px | 1.1 | Large numbers |
Font Weights
| Token | Weight | Usage |
|---|---|---|
--font-weight-light | 300 | Large titles |
--font-weight-normal | 400 | Body text |
--font-weight-medium | 500 | Buttons, emphasized |
--font-weight-semibold | 600 | Headers, labels |
--font-weight-bold | 700 | Stats, strong emphasis |
Spacing System
| Token | Value | Usage |
|---|---|---|
--space-0 | 0 | No spacing |
--space-1 | 4px | Tight, inline elements |
--space-2 | 8px | Component padding, gaps |
--space-3 | 12px | Card padding |
--space-4 | 16px | Section margins |
--space-5 | 20px | Larger gaps |
--space-6 | 24px | Panel padding |
--space-8 | 32px | Section spacing |
--space-10 | 40px | Large separations |
--space-12 | 48px | Page margins |
Border Radius
| Token | Value | Usage |
|---|---|---|
--radius-none | 0 | Sharp corners |
--radius-sm | 2px | Subtle rounding |
--radius-base | 4px | Inputs, buttons |
--radius-md | 6px | Cards |
--radius-lg | 8px | Panels, modals |
--radius-xl | 12px | Large cards |
--radius-full | 9999px | Pills, circles |
Shadows
| Token | Value | Usage |
|---|---|---|
--shadow-sm | 0 1px 2px rgba(0,0,0,0.05) | Subtle lift |
--shadow-base | 0 2px 4px rgba(0,0,0,0.1) | Standard cards |
--shadow-md | 0 4px 8px rgba(0,0,0,0.12) | Elevated cards |
--shadow-lg | 0 8px 16px rgba(0,0,0,0.15) | Dropdowns, popovers |
--shadow-xl | 0 12px 24px rgba(0,0,0,0.2) | Modals |
17.2 Component Specifications
1. StatCard
Purpose: Display key metrics with trend indicators on dashboards.
ASCII Wireframe:
┌────────────────────────────────────┐
│ [icon] │
│ │
│ LABEL │
│ 12,450 │
│ +12.3% vs previous │
│ │
└────────────────────────────────────┘
Variants:
STANDARD COMPACT INLINE
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐
│ [icon] │ │ Orders 1,234 │ │ [icon] Orders: 1,234 +5% │
│ Orders │ │ +12% ▲ │ └──────────────────────────┘
│ 1,234 │ └──────────────────┘
│ +12% ▲ │
└──────────────────┘
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Title | string | required | Metric label |
Value | string | required | Primary value |
Icon | IconType | null | Optional icon |
Change | string | null | Change indicator (e.g., “+12%”) |
IsPositive | bool | true | Trend direction |
Color | string | “primary” | primary, success, warning, error |
Size | string | “standard” | standard, compact, inline |
Blazor Usage:
<StatCard Title="Today's Sales"
Value="$12,450"
Icon="IconType.DollarSign"
Change="+12.3%"
IsPositive="true"
Color="success" />
2. DataGrid
Purpose: Display tabular data with sorting, filtering, and pagination.
ASCII Wireframe:
┌─────────────────────────────────────────────────────────────────────┐
│ [x] │ ORDER # ▼ │ DATE │ CUSTOMER │ AMOUNT ▼ │ STATUS │
├─────┼────────────┼────────────┼───────────────┼──────────┼─────────┤
│ [ ] │ #1234 │ 12/29/2024 │ John Smith │ $99.00 │ ● New │
│ [x] │ #1235 │ 12/29/2024 │ Jane Doe │ $149.00 │ ● Done │
│ [ ] │ #1236 │ 12/28/2024 │ Bob Johnson │ $75.50 │ ! Error │
├─────┴────────────┴────────────┴───────────────┴──────────┴─────────┤
│ Showing 1-50 of 256 << < Page 1 of 6 > >> │
└─────────────────────────────────────────────────────────────────────┘
Column Types:
TEXT COLUMN NUMBER COLUMN STATUS COLUMN ACTION COLUMN
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ John Smith │ │ $99.00 │ │ ● Completed │ │ [Ed] [Del] │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Left Right Center Center
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Items | IEnumerable | required | Data source |
Columns | List | required | Column definitions |
Selectable | bool | false | Enable row selection |
Sortable | bool | true | Enable column sorting |
Paginate | bool | true | Enable pagination |
PageSize | int | 25 | Items per page |
OnRowClick | EventCallback | null | Row click handler |
OnSelectionChange | EventCallback | null | Selection handler |
Column Definition:
public class Column<T>
{
public string Header { get; set; }
public Func<T, object> ValueFunc { get; set; }
public string Align { get; set; } = "left"; // left, center, right
public bool Sortable { get; set; } = true;
public string Width { get; set; } = "auto";
public Func<T, RenderFragment> Template { get; set; }
}
Blazor Usage:
<DataGrid Items="@orders" Selectable="true" OnRowClick="ViewOrder">
<Column Header="Order #" ValueFunc="@(o => o.OrderNumber)" />
<Column Header="Date" ValueFunc="@(o => o.Date.ToShortDateString())" />
<Column Header="Amount" ValueFunc="@(o => o.Total)" Align="right" />
<Column Header="Status">
<Template>
<StatusBadge Status="@context.Status" />
</Template>
</Column>
</DataGrid>
3. StatusBadge
Purpose: Display color-coded status indicators.
ASCII Wireframe:
SUCCESS WARNING ERROR INFO NEUTRAL
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│● Active │ │● Pending│ │● Failed │ │● Syncing│ │● Draft │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
Green bg Orange bg Red bg Blue bg Gray bg
Size Variants:
SMALL MEDIUM (Default) LARGE
┌──────────┐ ┌─────────────┐ ┌────────────────┐
│ ● Active │ │ ● Active │ │ ● Active │
└──────────┘ └─────────────┘ └────────────────┘
11px font 13px font 15px font
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Status | string | required | Status text |
Variant | string | “info” | success, warning, error, info, neutral |
Size | string | “medium” | small, medium, large |
ShowDot | bool | true | Show status dot |
CSS Classes:
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: var(--radius-base);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
}
.status-badge--success {
background: var(--color-success-light);
color: var(--color-success-dark);
}
.status-badge--warning {
background: var(--color-warning-light);
color: var(--color-warning-dark);
}
.status-badge--error {
background: var(--color-error-light);
color: var(--color-error-dark);
}
.status-badge--info {
background: var(--color-info-light);
color: var(--color-info-dark);
}
.status-badge--neutral {
background: var(--color-gray-100);
color: var(--color-gray-700);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
Blazor Usage:
<StatusBadge Status="Active" Variant="success" />
<StatusBadge Status="Pending" Variant="warning" />
<StatusBadge Status="Failed" Variant="error" ShowDot="false" />
4. SearchInput
Purpose: Debounced search input with autocomplete support.
ASCII Wireframe:
EMPTY STATE WITH VALUE
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ [O] Search products... │ │ [O] galaxy v-neck [X] │
└────────────────────────────────┘ └────────────────────────────────┘
WITH AUTOCOMPLETE LOADING STATE
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ [O] galaxy v │ │ [O] galaxy v-neck [...] │
├────────────────────────────────┤ └────────────────────────────────┘
│ Galaxy V-Neck Tee │
│ Galaxy V-Neck Tank │
│ Galaxy Vintage Wash │
└────────────────────────────────┘
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Value | string | “” | Current value |
Placeholder | string | “Search…” | Placeholder text |
DebounceMs | int | 300 | Debounce delay |
AutoComplete | bool | false | Enable autocomplete |
Items | IEnumerable | null | Autocomplete items |
OnSearch | EventCallback | null | Search handler |
OnSelect | EventCallback | null | Selection handler |
Disabled | bool | false | Disable input |
Blazor Usage:
<SearchInput @bind-Value="searchTerm"
Placeholder="Search products..."
DebounceMs="300"
OnSearch="HandleSearch" />
<SearchInput @bind-Value="productSearch"
AutoComplete="true"
Items="@productSuggestions"
OnSelect="SelectProduct" />
5. Modal
Purpose: Overlay dialog for forms, confirmations, and detail views.
ASCII Wireframe:
STANDARD MODAL
┌────────────────────────────────────────────────────────────┐
│ Modal Title [X] │
├────────────────────────────────────────────────────────────┤
│ │
│ Modal content goes here. │
│ │
│ This can include forms, text, images, or any other │
│ content that needs to be displayed in an overlay. │
│ │
├────────────────────────────────────────────────────────────┤
│ [Cancel] [Confirm] │
└────────────────────────────────────────────────────────────┘
CONFIRMATION MODAL (Compact)
┌─────────────────────────────────────────────┐
│ [!] Delete Item? [X] │
├─────────────────────────────────────────────┤
│ │
│ Are you sure you want to delete this item? │
│ This action cannot be undone. │
│ │
├─────────────────────────────────────────────┤
│ [Cancel] [Delete] │
└─────────────────────────────────────────────┘
FULLSCREEN MODAL (Mobile)
╔═════════════════════════════════════════════╗
║ [<] Modal Title ║
╠═════════════════════════════════════════════╣
║ ║
║ Full content area ║
║ (scrollable) ║
║ ║
╠═════════════════════════════════════════════╣
║ [Primary Action] ║
╚═════════════════════════════════════════════╝
Size Variants:
| Size | Width | Use Case |
|---|---|---|
small | 400px | Confirmations, alerts |
medium | 600px | Forms, details |
large | 800px | Complex forms, tables |
fullscreen | 100% | Mobile, immersive |
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Title | string | null | Modal title |
IsOpen | bool | false | Visibility state |
Size | string | “medium” | small, medium, large, fullscreen |
ShowClose | bool | true | Show close button |
CloseOnOverlay | bool | true | Close on backdrop click |
OnClose | EventCallback | null | Close handler |
ChildContent | RenderFragment | required | Modal body |
Footer | RenderFragment | null | Footer actions |
Blazor Usage:
<Modal Title="Edit Product"
IsOpen="@showModal"
Size="medium"
OnClose="CloseModal">
<ChildContent>
<EditForm Model="@product">
<!-- Form fields -->
</EditForm>
</ChildContent>
<Footer>
<Button Variant="secondary" OnClick="CloseModal">Cancel</Button>
<Button Variant="primary" OnClick="SaveProduct">Save</Button>
</Footer>
</Modal>
6. Toast
Purpose: Non-blocking notifications that auto-dismiss.
ASCII Wireframe:
SUCCESS TOAST ERROR TOAST
┌──────────────────────────┐ ┌──────────────────────────┐
│ [check] Product saved │ │ [X] Failed to save │
│ successfully │ │ Please try again │
│ [X] │ │ [X] │
└──────────────────────────┘ └──────────────────────────┘
WARNING TOAST INFO TOAST
┌──────────────────────────┐ ┌──────────────────────────┐
│ [!] Low inventory │ │ [i] Sync completed │
│ Check stock levels │ │ 245 items updated │
│ [X] │ │ [X] │
└──────────────────────────┘ └──────────────────────────┘
TOAST WITH ACTION
┌──────────────────────────────────────────┐
│ [!] Order requires attention │
│ Missing shipping address │
│ [View] [Dismiss]│
└──────────────────────────────────────────┘
Position Options:
TOP-RIGHT (Default) TOP-CENTER BOTTOM-RIGHT
┌─────────────────┐ ┌─────────────────┐
│ │ │ │
│ [T] │ │ [T] │
│ [T] │ │ [T] │ ┌─────────────────┐
│ │ │ │ │ │
│ │ │ │ │ [T] │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Message | string | required | Toast message |
Title | string | null | Optional title |
Variant | string | “info” | success, warning, error, info |
Duration | int | 5000 | Auto-dismiss (ms), 0 = persist |
Position | string | “top-right” | Toast position |
ShowClose | bool | true | Show dismiss button |
Action | RenderFragment | null | Action buttons |
Toast Service:
public interface IToastService
{
void ShowSuccess(string message, string title = null);
void ShowError(string message, string title = null);
void ShowWarning(string message, string title = null);
void ShowInfo(string message, string title = null);
void Show(ToastOptions options);
void DismissAll();
}
Blazor Usage:
@inject IToastService Toast
<button @onclick="SaveProduct">Save</button>
@code {
async Task SaveProduct()
{
try
{
await productService.SaveAsync(product);
Toast.ShowSuccess("Product saved successfully");
}
catch
{
Toast.ShowError("Failed to save product", "Error");
}
}
}
7. LoadingSpinner
Purpose: Indicate loading states.
ASCII Wireframe:
SPINNER ONLY WITH TEXT OVERLAY
◐ ◐ ┌─────────────────┐
╱ ╲ Loading... │ ░░░░░░░░░ │
◜ ◝ │ ░ ◐ ░ │
│ ░Loading░ │
│ ░░░░░░░░░ │
└─────────────────┘
Size Variants:
| Size | Diameter | Use Case |
|---|---|---|
small | 16px | Inline, buttons |
medium | 24px | Cards, sections |
large | 48px | Page, full overlay |
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Size | string | “medium” | small, medium, large |
Text | string | null | Loading text |
Overlay | bool | false | Full overlay mode |
Color | string | “primary” | Spinner color |
Blazor Usage:
<!-- Inline spinner -->
<LoadingSpinner Size="small" />
<!-- With text -->
<LoadingSpinner Text="Saving..." />
<!-- Full overlay -->
<LoadingSpinner Overlay="true" Text="Processing order..." />
<!-- In button -->
<Button Disabled="@isSaving">
@if (isSaving)
{
<LoadingSpinner Size="small" Color="white" />
<span>Saving...</span>
}
else
{
<span>Save</span>
}
</Button>
8. EmptyState
Purpose: Display meaningful placeholder when no data is available.
ASCII Wireframe:
STANDARD EMPTY STATE
┌─────────────────────────────────────────────────────┐
│ │
│ [ ICON ] │
│ │
│ No products found │
│ │
│ Try adjusting your search or filters to │
│ find what you're looking for. │
│ │
│ [Clear Filters] │
│ │
└─────────────────────────────────────────────────────┘
COMPACT EMPTY STATE WITH ACTION
┌─────────────────────────┐ ┌─────────────────────────┐
│ [icon] │ │ [icon] │
│ No items found │ │ No orders yet │
└─────────────────────────┘ │ │
│ [Create Order] │
└─────────────────────────┘
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Icon | IconType | null | Illustration icon |
Title | string | required | Empty state title |
Description | string | null | Explanatory text |
Action | RenderFragment | null | Action button(s) |
Size | string | “medium” | compact, medium, large |
Blazor Usage:
<EmptyState Icon="IconType.Box"
Title="No products found"
Description="Try adjusting your search or filters.">
<Action>
<Button Variant="secondary" OnClick="ClearFilters">Clear Filters</Button>
</Action>
</EmptyState>
17.3 Button Component
Purpose: Primary interactive element for triggering actions.
ASCII Wireframe:
PRIMARY SECONDARY TERTIARY/TEXT
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Save │ │ Cancel │ │ Learn More │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Solid background Outlined No border
DANGER WITH ICON LOADING
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Delete │ │ [+] Add Item │ │ [o] Saving... │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Red background Icon + text Spinner + text
Size Variants:
| Size | Height | Padding | Font Size |
|---|---|---|---|
small | 28px | 8px 12px | 12px |
medium | 36px | 10px 16px | 14px |
large | 44px | 12px 20px | 16px |
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Variant | string | “primary” | primary, secondary, tertiary, danger |
Size | string | “medium” | small, medium, large |
Icon | IconType | null | Leading icon |
IconPosition | string | “left” | left, right |
Loading | bool | false | Show loading state |
Disabled | bool | false | Disable button |
FullWidth | bool | false | 100% width |
OnClick | EventCallback | null | Click handler |
17.4 Form Components
TextInput
LABEL WITH INPUT ERROR STATE
┌────────────────────────────┐ ┌────────────────────────────┐
│ Email Address │ │ Email Address │
│ ┌────────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ user@example.com │ │ │ │ invalid-email │ │
│ └────────────────────────┘ │ │ └────────────────────────┘ │
└────────────────────────────┘ │ Please enter a valid email │
└────────────────────────────┘
Select/Dropdown
CLOSED OPEN
┌────────────────────────────┐ ┌────────────────────────────┐
│ Select option [v] │ │ Option One [^] │
└────────────────────────────┘ ├────────────────────────────┤
│ Option One [check] │
│ Option Two │
│ Option Three │
└────────────────────────────┘
Checkbox
UNCHECKED CHECKED INDETERMINATE
[ ] Option One [x] Option Two [-] Select All
Radio Button
UNSELECTED SELECTED
( ) Option One (o) Option Two
17.5 Dark Mode Considerations
Color Mapping
| Light Mode | Dark Mode |
|---|---|
| #FFFFFF (white) | #1E1E1E (dark surface) |
| #F5F5F5 (gray-100) | #2D2D2D (elevated surface) |
| #212121 (text) | #FFFFFF (text) |
| #757575 (secondary) | #B0B0B0 (secondary) |
| #1976D2 (primary) | #64B5F6 (lighter primary) |
Dark Mode Tokens
:root[data-theme="dark"] {
--color-background: #121212;
--color-surface: #1E1E1E;
--color-surface-elevated: #2D2D2D;
--color-text-primary: #FFFFFF;
--color-text-secondary: #B0B0B0;
--color-text-disabled: #6B6B6B;
--color-border: #3D3D3D;
--color-primary: #64B5F6;
--color-primary-dark: #90CAF9;
}
Component Adjustments
| Component | Light | Dark |
|---|---|---|
| Cards | White bg, shadow | Dark surface, border |
| Inputs | White bg, gray border | Dark bg, light border |
| Badges | Colored bg | Reduced opacity bg |
| Buttons | Standard | Slightly elevated |
17.6 Accessibility (WCAG 2.1 AA)
All components must meet WCAG 2.1 Level AA compliance. This is non-negotiable for a POS system used in retail environments with diverse staff.
Color Contrast Requirements
| Requirement | Ratio | Applies To |
|---|---|---|
| AA Normal text | 4.5:1 | Body text (< 18px), input labels |
| AA Large text | 3:1 | Text 18px+ or 14px+ bold, headings |
| AA UI Components | 3:1 | Borders, icons, focus indicators, form controls |
| AAA Normal (goal) | 7:1 | Critical POS text (totals, prices, error messages) |
Focus Management
/* Visible focus ring for keyboard navigation */
*:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* High contrast mode */
@media (prefers-contrast: high) {
*:focus-visible {
outline-width: 3px;
outline-color: Highlight;
}
}
/* Never remove focus outlines -- override only with visible alternatives */
Focus management patterns:
- Modal open: Focus moves to first focusable element inside modal
- Modal close: Focus returns to the element that triggered the modal
- Toast: Does not steal focus; announced via
aria-live="polite" - DataGrid pagination: Focus stays on grid after page change
- Cart item removal: Focus moves to next cart item (or previous if last)
Keyboard Navigation
| Component | Keys | Behavior |
|---|---|---|
| Button | Enter, Space | Activate |
| Modal | Escape | Close |
| DataGrid | Arrow Up/Down | Navigate rows |
| DataGrid | Enter | Select row / trigger action |
| SearchInput | Arrow Down | Open autocomplete |
| SearchInput | Escape | Close autocomplete |
| Select | Arrow Up/Down | Navigate options |
| Select | Enter | Select option |
| Numpad (POS) | 0-9 | Enter digits |
| Numpad (POS) | Backspace | Clear last digit |
Screen Reader Support
<!-- Button with icon only -- MUST have aria-label -->
<Button Icon="IconType.Search" aria-label="Search products" />
<!-- Loading state -- announced as status -->
<LoadingSpinner aria-label="Loading content" role="status" />
<!-- Status badge with full context -->
<StatusBadge Status="Error"
aria-label="Order status: Error - requires attention" />
<!-- Live region for cart updates -->
<div aria-live="polite" aria-atomic="false">
@foreach (var item in cart)
{
<CartItem Item="@item" />
}
</div>
<!-- Descriptive form labels -->
<label for="qty-input">Quantity for @item.Name</label>
<input id="qty-input" type="number"
aria-describedby="qty-help"
min="1" max="999" />
<span id="qty-help">Enter quantity between 1 and 999</span>
Touch Target Minimums
| Context | Minimum Size | Rationale |
|---|---|---|
| POS terminal | 44x44px | WCAG 2.5.5 target size, touch-friendly for gloved hands |
| Admin portal | 44x44px | Standard web accessibility minimum |
| Mobile Raptag | 48x48px | Material Design guidance for handheld devices |
| Numpad buttons | 64x64px | Fast, accurate PIN entry and quantity input |
17.7 Component State Patterns
All data-driven components follow a standard state machine to ensure consistent loading, empty, and error UX:
┌──────────┐
│ │
┌────│ Loading │────┐
│ │ │ │
│ └──────────┘ │
▼ ▼
┌──────────┐ ┌──────────┐
│ │ │ │
│Populated │ │ Empty │
│ │ │ │
└──────────┘ └──────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ │ │ │
│ Error │────────▶│ Loading │ (retry)
│ │ │ │
└──────────┘ └──────────┘
State Definitions:
| State | Visual | Component |
|---|---|---|
| Loading | <LoadingSpinner> centered in container | Spinner with optional text |
| Populated | Normal content rendering | Data grid, cards, lists |
| Empty | <EmptyState> with action | Icon + message + optional CTA |
| Error | <ErrorState> with retry | Error icon + message + retry button |
Blazor Pattern:
@if (isLoading)
{
<LoadingSpinner Text="Loading orders..." />
}
else if (hasError)
{
<EmptyState Icon="IconType.AlertCircle"
Title="Failed to load orders"
Description="@errorMessage">
<Action>
<Button Variant="secondary" OnClick="RetryLoad">Retry</Button>
</Action>
</EmptyState>
}
else if (!items.Any())
{
<EmptyState Icon="IconType.Box"
Title="No orders found"
Description="Try adjusting your filters." />
}
else
{
<DataGrid Items="@items" ... />
}
17.8 Offline Indicator Component
Purpose: Show network connectivity and sync status across all POS Client screens. This component appears in the status bar of every POS screen.
ASCII Wireframe:
ONLINE SYNCING QUEUED
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ ● Online │ │ ◐ Syncing... │ │ ● Queued (12) │
└───────────────────┘ └───────────────────┘ └───────────────────┘
Green dot Animated blue dot Yellow dot + count
ERROR
┌───────────────────┐
│ ✕ Offline (!) │
│ [Retry] │
└───────────────────┘
Red dot + retry
States:
| State | Dot Color | Label | Badge | Action |
|---|---|---|---|---|
| Online | Green | “Online” | None | None |
| Syncing | Blue (animated) | “Syncing…” | None | None |
| Queued | Yellow | “Queued” | Count of pending items | Tap to view queue |
| Error | Red | “Offline” | Exclamation | [Retry] button |
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
Status | ConnectionStatus | required | Current connection state |
PendingCount | int | 0 | Items awaiting sync |
LastSyncAt | DateTime? | null | Timestamp of last successful sync |
OnRetry | EventCallback | null | Retry handler for error state |
Blazor Usage:
<OfflineIndicator Status="@syncService.ConnectionStatus"
PendingCount="@syncService.PendingCount"
LastSyncAt="@syncService.LastSyncAt"
OnRetry="syncService.ForceSync" />
17.9 Summary
The Component Library provides:
- StatCard: Dashboard metrics with trends
- DataGrid: Sortable, filterable data tables
- StatusBadge: Color-coded status indicators
- SearchInput: Debounced search with autocomplete
- Modal: Overlay dialogs for forms and confirmations
- Toast: Non-blocking notifications
- LoadingSpinner: Loading state indicators
- EmptyState: Meaningful placeholders
- OfflineIndicator: Network status with sync queue count
All components follow:
- Consistent design tokens
- Responsive sizing
- Dark mode support
- WCAG 2.1 AA accessibility compliance
- Standard component state patterns (Loading/Populated/Empty/Error)
Next: Part VI covers the Implementation Guide starting with Chapter 18: Development Environment.
Document Information
| Attribute | Value |
|---|---|
| Version | 5.0.0 |
| Created | 2025-12-29 |
| Updated | 2026-02-25 |
| Author | Claude Code |
| Status | Active |
| Part | V - Frontend |
| Chapter | 17 of 32 |
This chapter is part of the POS Blueprint Book. All content is self-contained.