Architecture rules added: - FE-008: Use number_stepper macro for quantity inputs - FE-009: Use product_card macro for product displays - FE-010: Use product_grid macro for product listings - FE-011: Use add_to_cart macros for cart interactions - FE-012: Use mini_cart macro for cart dropdown Documentation: - Update ui-components-quick-reference.md with e-commerce section - Add component-standards.md for standardization guidelines - Add ecommerce-components-proposal.md with full 20-component roadmap - Update validate_architecture.py with FE-008 detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
13 KiB
UI Components Quick Reference
Most Common Patterns
📝 Form Field (Basic)
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Name</span>
<input
type="text"
x-model="formData.field"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
/>
</label>
📝 Required Field with Error
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Field Name <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.field"
required
:class="{ 'border-red-600': errors.field }"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
/>
<span x-show="errors.field" class="text-xs text-red-600 dark:text-red-400" x-text="errors.field"></span>
</label>
📝 Read-Only Field
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Name</span>
<input
type="text"
x-model="data.field"
disabled
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
/>
</label>
🃏 Stats Card
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Label</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">Value</p>
</div>
</div>
🃏 Info Card
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">Title</h3>
<div class="space-y-3">
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Label</p>
<p class="text-sm text-gray-700 dark:text-gray-300">Value</p>
</div>
</div>
</div>
🔘 Primary Button
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
Click Me
</button>
🔘 Button with Icon
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
🔘 Secondary Button
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
🏷️ Status Badge (Success)
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-green-700 bg-green-100 rounded-full dark:bg-green-700 dark:text-green-100">
<span x-html="$icon('check-circle', 'w-3 h-3 mr-1')"></span>
Active
</span>
🏷️ Status Badge (Warning)
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-orange-700 bg-orange-100 rounded-full dark:bg-orange-700 dark:text-orange-100">
<span x-html="$icon('clock', 'w-3 h-3 mr-1')"></span>
Pending
</span>
🏷️ Status Badge (Danger)
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-red-700 bg-red-100 rounded-full dark:bg-red-700 dark:text-red-100">
<span x-html="$icon('x-circle', 'w-3 h-3 mr-1')"></span>
Inactive
</span>
Number Stepper
A number input with +/- buttons for quantity selection. Ideal for cart quantities, batch sizes, and product pages.
Basic Number Stepper
{% from 'shared/macros/inputs.html' import number_stepper %}
{# Basic usage - cart quantity #}
{{ number_stepper(model='quantity', min=1, max=99) }}
Size Variants
{# Small - compact for tables/lists #}
{{ number_stepper(model='item.qty', min=1, max='item.stock', size='sm') }}
{# Medium (default) #}
{{ number_stepper(model='quantity', min=1, max=99) }}
{# Large - prominent placement #}
{{ number_stepper(model='batchSize', min=100, max=5000, step=100, size='lg') }}
With Disabled State
{{ number_stepper(model='qty', min=1, disabled_var='isLoading') }}
Number Stepper Parameters
| Parameter | Default | Description |
|---|---|---|
model |
required | Alpine.js x-model variable |
min |
1 |
Minimum allowed value |
max |
none |
Maximum allowed value (can be Alpine.js expression) |
step |
1 |
Increment/decrement step |
size |
'md' |
Size variant: 'sm', 'md', 'lg' |
disabled_var |
none |
Alpine.js variable for disabled state |
name |
none |
Input name for form submission |
id |
none |
Input id attribute |
label |
'Quantity' |
Accessible label for screen readers |
Tabs
Tab navigation components for switching between content sections.
🗂️ Navigation Tabs (with icons)
{% from 'shared/macros/tabs.html' import tabs_nav, tab_button %}
{% call tabs_nav() %}
{{ tab_button('dashboard', 'Dashboard', icon='home') }}
{{ tab_button('settings', 'Settings', icon='cog') }}
{{ tab_button('profile', 'Profile', icon='user') }}
{% endcall %}
<!-- Tab content panels -->
<div x-show="activeTab === 'dashboard'" x-transition>
Dashboard content...
</div>
🗂️ Inline Tabs (with count badges)
{% from 'shared/macros/tabs.html' import tabs_inline, tab_button %}
<div class="flex justify-between gap-4">
{% call tabs_inline() %}
{{ tab_button('all', 'All Items', count_var='allItems.length') }}
{{ tab_button('active', 'Active', count_var='activeItems.length') }}
{{ tab_button('archived', 'Archived', count_var='archivedItems.length') }}
{% endcall %}
<div>Search...</div>
</div>
🗂️ Tabs with Custom Click Handlers
{% call tabs_nav() %}
{{ tab_button('database', 'Database Logs',
tab_var='logSource',
icon='database',
onclick="logSource = 'database'; loadDatabaseLogs()") }}
{{ tab_button('file', 'File Logs',
tab_var='logSource',
icon='document',
onclick="logSource = 'file'; loadFileLogs()") }}
{% endcall %}
Tab Button Parameters
| Parameter | Default | Description |
|---|---|---|
id |
required | Tab identifier for comparison |
label |
required | Display text |
tab_var |
'activeTab' |
Alpine.js variable for active state |
icon |
none |
Optional icon name |
count_var |
none |
Alpine.js variable for count badge |
onclick |
none |
Custom click handler (overrides default) |
Grid Layouts
2 Columns (Desktop)
<div class="grid gap-6 md:grid-cols-2">
<!-- Column 1 -->
<div>...</div>
<!-- Column 2 -->
<div>...</div>
</div>
4 Columns (Responsive)
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Cards -->
</div>
Color Classes
Background Colors
- Primary:
bg-purple-600 - Success:
bg-green-600 - Warning:
bg-orange-600 - Danger:
bg-red-600 - Info:
bg-blue-600
Text Colors
- Primary:
text-purple-600 - Success:
text-green-600 - Warning:
text-orange-600 - Danger:
text-red-600 - Info:
text-blue-600
Icon Colors
- Primary:
text-purple-500 bg-purple-100 - Success:
text-green-500 bg-green-100 - Warning:
text-orange-500 bg-orange-100 - Danger:
text-red-500 bg-red-100 - Info:
text-blue-500 bg-blue-100
Common Icons
user-group- Users/Teamsbadge-check- Verifiedcheck-circle- Successx-circle- Error/Inactiveclock- Pendingcalendar- Datesrefresh- Updateedit- Editdelete- Deleteplus- Addarrow-left- Backexclamation- Warning
Spacing
- Small gap:
gap-3 - Medium gap:
gap-6 - Large gap:
gap-8 - Margin bottom:
mb-4,mb-6,mb-8 - Padding:
p-3,p-4,px-4 py-3
Quick Copy-Paste: Page Structure
{# app/templates/admin/your-page.html #}
{% extends "admin/base.html" %}
{% block title %}Your Page{% endblock %}
{% block alpine_data %}yourPageData(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Page Title
</h2>
</div>
<!-- Loading State -->
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
</div>
<!-- Content -->
<div x-show="!loading">
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
<!-- Your content here -->
</div>
</div>
{% endblock %}
Remember
- Always use
dark:variants for dark mode - Add
:disabled="saving"to buttons during operations - Use
x-showfor conditional display - Use
x-textfor dynamic text - Use
x-html="$icon(...)"for icons - Validation errors:
border-red-600class - Helper text:
text-xs text-gray-600 - Error text:
text-xs text-red-600
E-commerce Components (Shop Frontend)
Reusable macros for shop/storefront functionality. Located in app/templates/shared/macros/shop/.
🛍️ Product Card
{% from 'shared/macros/shop/product-card.html' import product_card %}
{# Basic product card #}
{{ product_card(product_var='product') }}
{# With size and options #}
{{ product_card(
product_var='item',
size='lg',
show_rating=true,
show_quick_add=true,
show_wishlist=true
) }}
Size variants: sm (compact), md (default), lg (featured)
Features:
- Sale badge (when
sale_priceexists) - "New" badge (when
is_newis true) - Out of stock overlay
- Star ratings with review count
- Wishlist toggle button
- Quick add to cart
🛍️ Product Grid
{% from 'shared/macros/shop/product-grid.html' import product_grid %}
{# Basic grid #}
{{ product_grid(products_var='products', loading_var='loading') }}
{# With empty state #}
{{ product_grid(
products_var='searchResults',
loading_var='searching',
empty_message='No products found',
empty_icon='search'
) }}
Features:
- Responsive columns (auto-adjusts or fixed)
- Loading skeleton placeholders
- Empty state with customizable icon/message
🛒 Add to Cart
{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form, buy_now_button %}
{# Simple button #}
{{ add_to_cart_button(action='addToCart()') }}
{# Complete form with quantity #}
{{ add_to_cart_form(product_var='product', size='md') }}
{# Buy now button #}
{{ buy_now_button(action='buyNow()') }}
Macros available:
add_to_cart_button()- Simple button with loading stateadd_to_cart_form()- Form with quantity selector + buttonbuy_now_button()- Direct checkout buttonshop_quantity_selector()- Stock-aware quantity input
🛒 Mini Cart (Header)
{% from 'shared/macros/shop/mini-cart.html' import mini_cart, mini_cart_icon %}
{# Complete mini cart with dropdown #}
{{ mini_cart(cart_var='cart', show_var='showCart') }}
{# Just the icon with badge #}
{{ mini_cart_icon(cart_var='cart', size='md') }}
Macros available:
mini_cart()- Combined icon + dropdownmini_cart_icon()- Icon with item count badgemini_cart_dropdown()- Dropdown panelcart_item()- Individual item displaycart_summary()- Subtotal, shipping, total
E-commerce Alpine.js State
// Required state variables for e-commerce components
{
// Products
products: [],
loading: true,
// Cart
cart: {
items: [],
item_count: 0,
subtotal: 0,
total: 0
},
showCart: false,
// Add to cart
quantity: 1,
addingToCart: false,
addedToCart: false,
// Wishlist
toggleWishlist(product) {
product.in_wishlist = !product.in_wishlist;
},
// Cart actions
addToCart() {
this.addingToCart = true;
// API call...
},
removeFromCart(itemId) {
this.cart.items = this.cart.items.filter(i => i.id !== itemId);
}
}
Reference Page
Visit /admin/components for full component library with live examples!