Files
orion/docs/frontend/shared/component-standards.md
Samir Boulahtit 95a8ffc645 docs: add architecture rules and docs for e-commerce components
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>
2025-12-07 17:04:28 +01:00

12 KiB

Frontend Component Standards

Version: 1.0 Last Updated: December 2025 Audience: Frontend Developers


Overview

This document defines mandatory standards for all frontend components across Admin, Vendor, and Shop frontends. Following these standards ensures consistency, maintainability, and a unified user experience.


Golden Rules

  1. Use Jinja Macros - Never copy-paste HTML patterns; use shared macros
  2. Alpine.js for Interactivity - All client-side logic uses Alpine.js
  3. Dark Mode Required - Every component must support dark mode
  4. Accessibility First - ARIA labels, keyboard navigation, focus states
  5. Mobile Responsive - Mobile-first design with Tailwind breakpoints

Jinja Macro System

Available Macro Files

All macros are located in app/templates/shared/macros/:

File Purpose Key Macros
pagination.html Table pagination pagination(), pagination_simple()
alerts.html Alerts and toasts loading_state(), error_state(), alert_dynamic()
badges.html Status indicators badge(), status_badge(), role_badge()
buttons.html Button variants btn_primary(), btn_secondary(), action_button()
forms.html Form inputs form_input(), form_select(), form_textarea()
tables.html Table components table_wrapper(), table_header(), table_empty_state()
cards.html Card layouts stat_card(), card(), info_card()
headers.html Page headers page_header(), page_header_flex(), refresh_button()
modals.html Modal dialogs modal(), confirm_modal(), job_details_modal()
tabs.html Tab navigation tabs_nav(), tabs_inline(), tab_button()
inputs.html Specialized inputs search_autocomplete(), number_stepper()

Macro Usage Rules

RULE 1: Always Import Before Use

{# ✅ CORRECT - Import at top of template #}
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
{% from 'shared/macros/pagination.html' import pagination %}

{% block content %}
    {% call table_wrapper() %}
        {{ table_header(['Name', 'Email', 'Status']) }}
        ...
    {% endcall %}
    {{ pagination() }}
{% endblock %}

{# ❌ WRONG - Inline HTML instead of macro #}
{% block content %}
    <div class="w-full overflow-hidden rounded-lg shadow-xs">
        <div class="w-full overflow-x-auto">
            <table class="w-full whitespace-no-wrap">
                ...
            </table>
        </div>
    </div>
{% endblock %}

RULE 2: Use Macros for Repeated Patterns

If you find yourself copying HTML more than once, create or use a macro.

{# ✅ CORRECT - Using badge macro #}
{{ status_badge(status='active') }}
{{ status_badge(status='pending') }}
{{ status_badge(status='inactive') }}

{# ❌ WRONG - Copy-pasting badge HTML #}
<span class="px-2 py-1 text-xs font-semibold text-green-800 bg-green-100 rounded-full">Active</span>
<span class="px-2 py-1 text-xs font-semibold text-yellow-800 bg-yellow-100 rounded-full">Pending</span>

RULE 3: Use {% call %} for Wrapper Macros

Macros that wrap content use the caller() pattern:

{# ✅ CORRECT - Using call block #}
{% call table_wrapper() %}
    {{ table_header(['Column 1', 'Column 2']) }}
    <tbody>...</tbody>
{% endcall %}

{% call tabs_nav() %}
    {{ tab_button('tab1', 'First Tab', icon='home') }}
    {{ tab_button('tab2', 'Second Tab', icon='cog') }}
{% endcall %}

RULE 4: Prefer Macro Parameters Over Custom CSS

{# ✅ CORRECT - Using size parameter #}
{{ number_stepper(model='qty', size='sm') }}
{{ number_stepper(model='qty', size='lg') }}

{# ❌ WRONG - Adding custom classes #}
{{ number_stepper(model='qty') }}
<style>.my-stepper { transform: scale(0.8); }</style>

Component Categories

1. Layout Components

Page Headers

{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}

{% call page_header_flex(title='Page Title', subtitle='Optional description') %}
    {{ refresh_button(onclick='refreshData()') }}
    <button class="...">Add New</button>
{% endcall %}

Cards

{% from 'shared/macros/cards.html' import stat_card %}

{{ stat_card(
    label='Total Users',
    value='1,234',
    icon='user-group',
    color='purple'
) }}

2. Data Display Components

Tables with Pagination

{% from 'shared/macros/tables.html' import table_wrapper, table_header, table_empty_state %}
{% from 'shared/macros/pagination.html' import pagination %}

{% call table_wrapper() %}
    {{ table_header(['Name', 'Email', 'Status', 'Actions']) }}
    <tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
        <template x-for="item in items" :key="item.id">
            <tr class="text-gray-700 dark:text-gray-400">
                ...
            </tr>
        </template>
    </tbody>
{% endcall %}

{{ table_empty_state(
    message='No items found',
    icon='inbox',
    show_condition='!loading && items.length === 0'
) }}

{{ pagination() }}

Badges

{% from 'shared/macros/badges.html' import status_badge %}

{{ status_badge(status='active') }}      {# Green #}
{{ status_badge(status='pending') }}     {# Yellow #}
{{ status_badge(status='inactive') }}    {# Red #}
{{ status_badge(status='processing') }}  {# Blue #}

3. Form Components

Standard Inputs

{% from 'shared/macros/forms.html' import form_input, form_select %}

{{ form_input(
    name='email',
    label='Email Address',
    type='email',
    model='formData.email',
    required=true,
    error_var='errors.email'
) }}

{{ form_select(
    name='status',
    label='Status',
    model='formData.status',
    options=[
        {'value': 'active', 'label': 'Active'},
        {'value': 'inactive', 'label': 'Inactive'}
    ]
) }}

Number Stepper

{% from 'shared/macros/inputs.html' import number_stepper %}

{# Cart quantity #}
{{ number_stepper(model='quantity', min=1, max=99, size='md') }}

{# Batch size with step #}
{{ number_stepper(model='batchSize', min=100, max=5000, step=100, size='lg') }}

Search Autocomplete

{% from 'shared/macros/inputs.html' import search_autocomplete, selected_item_display %}

{{ search_autocomplete(
    search_var='searchQuery',
    results_var='searchResults',
    show_dropdown_var='showDropdown',
    loading_var='searching',
    select_action='selectItem(item)',
    display_field='name',
    secondary_field='email',
    placeholder='Search users...'
) }}

{{ selected_item_display(
    selected_var='selectedUser',
    display_field='name',
    secondary_field='email',
    clear_action='clearSelection()'
) }}

4. Navigation Components

Tabs

{% from 'shared/macros/tabs.html' import tabs_nav, tabs_inline, tab_button %}

{# Full-width navigation tabs #}
{% call tabs_nav() %}
    {{ tab_button('overview', 'Overview', icon='home') }}
    {{ tab_button('details', 'Details', icon='document') }}
    {{ tab_button('settings', 'Settings', icon='cog') }}
{% endcall %}

{# Inline tabs with counts #}
{% call tabs_inline() %}
    {{ tab_button('all', 'All', count_var='items.length') }}
    {{ tab_button('active', 'Active', count_var='activeCount') }}
    {{ tab_button('archived', 'Archived', count_var='archivedCount') }}
{% endcall %}

5. Feedback Components

Alerts

{% from 'shared/macros/alerts.html' import alert_dynamic, loading_state, error_state %}

{{ alert_dynamic(type='success', message_var='successMessage', show_condition='successMessage') }}
{{ alert_dynamic(type='error', message_var='errorMessage', show_condition='errorMessage') }}

{{ loading_state(message='Loading data...', show_condition='loading') }}
{{ error_state(title='Error', show_condition='error') }}

Modals

{% from 'shared/macros/modals.html' import confirm_modal %}

{{ confirm_modal(
    show_var='showDeleteModal',
    title='Confirm Delete',
    message='Are you sure you want to delete this item?',
    confirm_action='deleteItem()',
    confirm_text='Delete',
    confirm_class='bg-red-600 hover:bg-red-700'
) }}

Dark Mode Requirements

Every component MUST support dark mode using Tailwind's dark: prefix.

Color Pairing Reference

Element Light Mode Dark Mode
Background bg-white dark:bg-gray-800
Card Background bg-gray-50 dark:bg-gray-900
Text Primary text-gray-700 dark:text-gray-200
Text Secondary text-gray-500 dark:text-gray-400
Border border-gray-300 dark:border-gray-600
Hover Background hover:bg-gray-100 dark:hover:bg-gray-700
Input Background bg-white dark:bg-gray-700
Focus Ring focus:ring-purple-500 dark:focus:ring-purple-400

Example

<div class="bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 border border-gray-300 dark:border-gray-600">
    <h3 class="text-gray-900 dark:text-gray-100">Title</h3>
    <p class="text-gray-500 dark:text-gray-400">Description</p>
</div>

Accessibility Requirements

Required ARIA Attributes

{# Button with loading state #}
<button :aria-busy="loading" :disabled="loading">
    <span x-show="!loading">Submit</span>
    <span x-show="loading">Loading...</span>
</button>

{# Icon-only button #}
<button aria-label="Delete item" title="Delete">
    <span x-html="$icon('trash', 'w-4 h-4')"></span>
</button>

{# Number stepper group #}
<div role="group" aria-label="Quantity selector">
    <button aria-label="Decrease quantity">-</button>
    <input aria-label="Quantity" />
    <button aria-label="Increase quantity">+</button>
</div>

Focus States

All interactive elements must have visible focus states:

<button class="focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2">
    Click me
</button>

Icon System

Usage

{# Standard icon #}
<span x-html="$icon('user', 'w-5 h-5')"></span>

{# Icon with color #}
<span x-html="$icon('check-circle', 'w-5 h-5 text-green-500')"></span>

{# Inline icon in button #}
<button class="flex items-center">
    <span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
    Add Item
</button>

Common Icons Reference

Icon Usage
home Dashboard/Home
user Users/Profile
cog Settings
plus Add/Create
edit Edit/Modify
trash Delete
check-circle Success/Verified
x-circle Error/Failed
clock Pending/Time
refresh Reload/Sync
eye View/Details
download Download/Export
upload Upload/Import
search Search
filter Filter
minus Decrease

Creating New Components

When to Create a Macro

Create a new macro when:

  • Pattern is used 3+ times across templates
  • Component has configurable options
  • Component requires consistent styling

Macro Template

{#
  Component Name
  ==============
  Brief description of what the component does.

  Parameters:
    - param1: Description (required/optional, default: value)
    - param2: Description

  Usage:
    {{ component_name(param1='value', param2='value') }}
#}
{% macro component_name(
    param1,
    param2='default'
) %}
<div class="...">
    {# Component HTML #}
</div>
{% endmacro %}

Checklist for New Components

  • Supports dark mode (dark: variants)
  • Has ARIA labels for accessibility
  • Documented with JSDoc-style comment
  • Added to /admin/components page
  • Added to this documentation
  • Uses existing design tokens (colors, spacing)
  • Tested in both light and dark mode
  • Works on mobile viewport

Component Reference Page

For live examples and copy-paste code, visit:

Admin Panel: /admin/components

This page includes:

  • Interactive demos
  • Code snippets with copy button
  • All available macros
  • Usage examples