# Jinja Macros Library **Version:** 1.0 **Last Updated:** December 2024 **Location:** `app/templates/shared/macros/` --- ## Overview The Jinja macros library provides reusable UI components for building consistent admin pages. All macros are built with: - **Tailwind CSS** for styling - **Alpine.js** for interactivity - **Dark mode** support built-in - **Accessibility** best practices ### Quick Start ```jinja {% from 'shared/macros/buttons.html' import btn_primary %} {% from 'shared/macros/forms.html' import form_input %} {% from 'shared/macros/modals.html' import confirm_modal %} {{ btn_primary('Save Changes', onclick='saveForm()') }} {{ form_input('Email', 'email', 'formData.email', type='email', required=true) }} {{ confirm_modal('deleteModal', 'Delete Item', 'Are you sure?', 'deleteItem()', 'showDeleteModal') }} ``` --- ## Available Macro Files | File | Macros | Description | |------|--------|-------------| | [alerts.html](#alerts) | 5 | Loading states, error alerts, toasts | | [avatars.html](#avatars) | 7 | User avatars with status indicators | | [badges.html](#badges) | 8 | Status badges, role badges, counts | | [buttons.html](#buttons) | 11 | Primary, secondary, danger, icon buttons | | [cards.html](#cards) | 7 | Stats cards, info cards, filter cards | | [charts.html](#charts) | 7 | Chart.js integration with Card wrappers | | [datepicker.html](#datepicker) | 6 | Flatpickr date/time pickers | | [dropdowns.html](#dropdowns) | 8 | Dropdown menus, context menus | | [forms.html](#forms) | 12 | Input fields, selects, checkboxes | | [headers.html](#headers) | 6 | Page headers, breadcrumbs, tabs | | [modals.html](#modals) | 5 + patterns | Modal dialogs, slide-overs, detail patterns | | [pagination.html](#pagination) | 2 | Table pagination controls | | [tables.html](#tables) | 10 | Table wrappers, cells, empty states | **Total: 94 macros** --- ## Alerts **File:** `shared/macros/alerts.html` ```jinja {% from 'shared/macros/alerts.html' import loading_state, error_state, alert, toast %} ``` ### loading_state Shows a centered loading spinner with message. ```jinja {{ loading_state(show_condition='loading', message='Loading data...') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `show_condition` | string | `'loading'` | Alpine.js condition | | `message` | string | `'Loading...'` | Loading message | ### error_state Shows an error alert with icon. ```jinja {{ error_state(show_condition='error', error_var='errorMessage') }} ``` ### alert Static alert box with variants. ```jinja {{ alert('Operation completed successfully!', variant='success', dismissible=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `message` | string | required | Alert message | | `variant` | string | `'info'` | `'success'`, `'warning'`, `'error'`, `'info'` | | `dismissible` | bool | `false` | Show close button | | `icon` | string | auto | Icon name | ### toast Toast notification (auto-dismiss). ```jinja {{ toast('Item saved!', 'success') }} ``` --- ## Avatars **File:** `shared/macros/avatars.html` ```jinja {% from 'shared/macros/avatars.html' import avatar, avatar_with_status, avatar_initials, avatar_group %} ``` ### avatar Basic avatar component. ```jinja {# Static image #} {{ avatar(src='/images/user.jpg', alt='John Doe', size='lg') }} {# Dynamic with Alpine.js #} {{ avatar(src='user.avatar_url', alt='user.name', size='md', dynamic=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `src` | string | `''` | Image URL or Alpine.js expression | | `alt` | string | `''` | Alt text | | `size` | string | `'md'` | `'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` | | `dynamic` | bool | `false` | Whether src is Alpine.js expression | | `fallback_icon` | string | `'user'` | Icon when no image | **Sizes:** - `xs`: 24px - `sm`: 32px - `md`: 40px - `lg`: 48px - `xl`: 56px - `2xl`: 64px ### avatar_with_status Avatar with online/offline indicator. ```jinja {{ avatar_with_status(src='user.avatar', status='online', size='md', dynamic=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | `'online'` | `'online'`, `'offline'`, `'away'`, `'busy'` | ### avatar_initials Avatar showing initials. ```jinja {{ avatar_initials(initials='JD', size='md', color='purple') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `initials` | string | required | 1-2 characters | | `color` | string | `'purple'` | `'gray'`, `'purple'`, `'blue'`, `'green'`, `'red'`, `'yellow'`, `'orange'` | ### avatar_group Stacked avatar group. ```jinja {% call avatar_group(max=4, total_var='team.length', size='sm') %} {{ avatar_group_item(src='member.avatar', dynamic=true) }} {% endcall %} ``` ### user_avatar_card Avatar with name and subtitle. ```jinja {{ user_avatar_card(src='user.avatar', name='user.name', subtitle='user.role', size='md') }} ``` --- ## Badges **File:** `shared/macros/badges.html` ```jinja {% from 'shared/macros/badges.html' import badge, status_badge, role_badge, severity_badge %} ``` ### badge Generic badge. ```jinja {{ badge('New', variant='success', icon='check') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `text` | string | required | Badge text | | `variant` | string | `'gray'` | `'gray'`, `'green'`, `'red'`, `'yellow'`, `'blue'`, `'purple'` | | `icon` | string | `none` | Optional icon | | `size` | string | `'md'` | `'sm'`, `'md'` | ### status_badge Dynamic status badge based on boolean. ```jinja {{ status_badge(condition='item.is_active', true_text='Active', false_text='Inactive') }} ``` ### role_badge User role badge. ```jinja {{ role_badge(role='item.role') }} ``` Automatically colors: admin=red, manager=purple, staff=blue, viewer=gray ### severity_badge Severity level badge. ```jinja {{ severity_badge(severity='violation.severity') }} ``` Levels: critical=red, error=orange, warning=yellow, info=blue ### order_status_badge Order status badge. ```jinja {{ order_status_badge(status='order.status') }} ``` Statuses: pending, processing, shipped, delivered, cancelled, refunded ### count_badge Numeric count badge (notification style). ```jinja {{ count_badge(count='notifications.length', max=99) }} ``` --- ## Buttons **File:** `shared/macros/buttons.html` ```jinja {% from 'shared/macros/buttons.html' import btn, btn_primary, btn_secondary, btn_danger, action_button %} ``` ### btn Base button macro. ```jinja {{ btn('Click Me', variant='primary', size='md', icon='plus', onclick='doSomething()') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Button text | | `variant` | string | `'primary'` | `'primary'`, `'secondary'`, `'danger'`, `'success'`, `'ghost'` | | `size` | string | `'md'` | `'sm'`, `'md'`, `'lg'` | | `icon` | string | `none` | Icon name | | `icon_position` | string | `'left'` | `'left'`, `'right'` | | `onclick` | string | `none` | Alpine.js click handler | | `href` | string | `none` | Makes it a link | | `disabled` | string | `none` | Alpine.js disabled condition | | `loading` | string | `none` | Alpine.js loading condition | | `type` | string | `'button'` | Button type | ### Convenience Variants ```jinja {{ btn_primary('Save', icon='check', onclick='save()') }} {{ btn_secondary('Cancel', onclick='cancel()') }} {{ btn_danger('Delete', icon='trash', onclick='delete()') }} {{ btn_success('Approve', onclick='approve()') }} ``` ### action_button Icon-only action button for tables. ```jinja {{ action_button(icon='edit', onclick='edit(item)', variant='primary', title='Edit') }} {{ action_button(icon='trash', onclick='delete(item)', variant='danger', title='Delete') }} ``` ### action_button_group Group of action buttons. ```jinja {% call action_button_group() %} {{ action_button(icon='eye', href="'/items/' + item.id", variant='info', title='View') }} {{ action_button(icon='edit', onclick='edit(item)', variant='primary', title='Edit') }} {{ action_button(icon='trash', onclick='confirmDelete(item)', variant='danger', title='Delete') }} {% endcall %} ``` ### back_button Back navigation button. ```jinja {{ back_button(href='/admin/items', label='Back to List') }} ``` ### submit_button Form submit button with loading state. ```jinja {{ submit_button(label='Save Changes', loading_var='saving', loading_text='Saving...') }} ``` --- ## Cards **File:** `shared/macros/cards.html` ```jinja {% from 'shared/macros/cards.html' import stat_card, card, info_card, filter_card %} ``` ### stat_card Statistics card with icon and value. ```jinja {{ stat_card(icon='users', label='Total Users', value='stats.totalUsers', color='purple') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `icon` | string | required | Icon name | | `label` | string | required | Stat label | | `value` | string | required | Alpine.js expression | | `color` | string | `'orange'` | `'orange'`, `'green'`, `'blue'`, `'purple'`, `'red'`, `'yellow'`, `'teal'` | | `format` | string | `none` | Format function (e.g., `'formatCurrency'`) | ### stats_grid Grid container for stat cards. ```jinja {% call stats_grid(columns=4) %} {{ stat_card(icon='users', label='Users', value='stats.users', color='blue') }} {{ stat_card(icon='shopping-cart', label='Orders', value='stats.orders', color='green') }} {{ stat_card(icon='currency-dollar', label='Revenue', value='stats.revenue', color='purple', format='formatCurrency') }} {{ stat_card(icon='chart-bar', label='Growth', value='stats.growth', color='orange') }} {% endcall %} ``` ### card Basic card container. ```jinja {% call card(title='User Details', subtitle='View and edit user information') %}
Card content here
{% endcall %} ``` ### info_card Card with icon and description. ```jinja {{ info_card(icon='document', title='Documentation', description='View the full docs', color='blue', href='/docs') }} ``` ### filter_card Card for search and filter controls. ```jinja {% call filter_card() %} {{ search_input(x_model='filters.search', on_input='debouncedSearch()') }} {{ filter_select(x_model='filters.status', options=[...]) }} {% endcall %} ``` --- ## Charts **File:** `shared/macros/charts.html` **Prerequisites:** Requires Chart.js. See [CDN Fallback Strategy](../cdn-fallback-strategy.md). ```jinja {% from 'shared/macros/charts.html' import chart_card, line_chart, bar_chart, doughnut_chart, chart_config_script %} ``` ### Loading Chart.js In your page template: ```jinja {% block chartjs_script %} {% from 'shared/includes/optional-libs.html' import chartjs_loader %} {{ chartjs_loader() }} {% endblock %} ``` ### chart_card Chart in a card with title and optional menu. ```jinja {{ chart_card('salesChart', 'Monthly Sales', 'line', height='300px') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Canvas element ID | | `title` | string | required | Card title | | `chart_type` | string | `'line'` | `'line'`, `'bar'`, `'doughnut'`, `'pie'` | | `height` | string | `'300px'` | Chart height | | `show_menu` | bool | `true` | Show dropdown menu | ### Standalone Charts ```jinja {{ line_chart('revenueChart', height='250px') }} {{ bar_chart('ordersChart', height='300px') }} {{ doughnut_chart('categoryChart', size='200px') }} ``` ### chart_config_script Include Chart.js configuration helpers. ```jinja {{ chart_config_script() }} ``` **Available helper functions:** - `createChart(id, type, data, options)` - `createLineChart(id, labels, datasets, options)` - `createBarChart(id, labels, datasets, options)` - `createDoughnutChart(id, labels, data, colors, options)` **Color presets:** `chartColors.purple`, `chartColors.blue`, `chartColors.green`, `chartColors.red`, `chartColors.yellow`, `chartColors.gray` --- ## Datepicker **File:** `shared/macros/datepicker.html` **Prerequisites:** Requires Flatpickr. See [CDN Fallback Strategy](../cdn-fallback-strategy.md). ```jinja {% from 'shared/macros/datepicker.html' import datepicker, daterange_picker, datetime_picker, time_picker %} ``` ### Loading Flatpickr In your page template: ```jinja {% block flatpickr_css %} {% from 'shared/includes/optional-libs.html' import flatpickr_css_loader %} {{ flatpickr_css_loader() }} {% endblock %} {% block flatpickr_script %} {% from 'shared/includes/optional-libs.html' import flatpickr_loader %} {{ flatpickr_loader() }} {% endblock %} ``` ### datepicker Single date picker. ```jinja {{ datepicker('startDate', 'formData.startDate', label='Start Date', required=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Input ID | | `x_model` | string | required | Alpine.js model | | `label` | string | `none` | Field label | | `placeholder` | string | `'Select date'` | Placeholder text | | `format` | string | `'Y-m-d'` | Date format | | `min_date` | string | `none` | Minimum date | | `max_date` | string | `none` | Maximum date | | `required` | bool | `false` | Required field | ### daterange_picker Date range picker. ```jinja {{ daterange_picker('dateRange', 'filters.dateRange', label='Date Range') }} ``` ### datetime_picker Date and time picker. ```jinja {{ datetime_picker('scheduledAt', 'formData.scheduledAt', label='Schedule For', minute_increment=15) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `time_24hr` | bool | `true` | Use 24-hour format | | `minute_increment` | int | `5` | Minute step | ### time_picker Time-only picker. ```jinja {{ time_picker('startTime', 'formData.startTime', label='Start Time') }} ``` --- ## Dropdowns **File:** `shared/macros/dropdowns.html` ```jinja {% from 'shared/macros/dropdowns.html' import dropdown, action_dropdown, context_menu, dropdown_item, dropdown_divider %} ``` ### dropdown Button with dropdown menu. ```jinja {% call dropdown('Actions', position='right') %} {{ dropdown_item('Edit', onclick='edit()', icon='pencil') }} {{ dropdown_item('Duplicate', onclick='duplicate()', icon='document-duplicate') }} {{ dropdown_divider() }} {{ dropdown_item('Delete', onclick='delete()', icon='trash', variant='danger') }} {% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Button label | | `position` | string | `'right'` | `'left'`, `'right'` | | `variant` | string | `'secondary'` | `'primary'`, `'secondary'`, `'ghost'` | | `icon` | string | `'chevron-down'` | Button icon | | `width` | string | `'w-48'` | Dropdown width | ### action_dropdown Dropdown with loading/disabled state support for action buttons. Uses external Alpine.js state from parent component. ```jinja {% call action_dropdown( label='Run Scan', loading_label='Scanning...', open_var='scanDropdownOpen', loading_var='scanning', icon='search' ) %} {{ dropdown_item('Run All', 'runScan("all"); scanDropdownOpen = false') }} {{ dropdown_item('Run Selected', 'runScan("selected"); scanDropdownOpen = false') }} {% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Button label | | `loading_label` | string | `'Loading...'` | Label shown when loading | | `open_var` | string | `'isDropdownOpen'` | Alpine.js variable for open state | | `loading_var` | string | `'isLoading'` | Alpine.js variable for loading/disabled state | | `icon` | string | `none` | Button icon (before label) | | `position` | string | `'right'` | `'left'`, `'right'` | | `variant` | string | `'primary'` | `'primary'`, `'secondary'` | | `width` | string | `'w-48'` | Dropdown width | **Features:** - Button is disabled when `loading_var` is true - Shows spinner icon and `loading_label` when loading - Uses external state (no `x-data` wrapper - expects parent component to define variables) ### context_menu Three-dot context menu (for table rows). ```jinja {% call context_menu() %} {{ dropdown_item('View', href="'/items/' + item.id", icon='eye') }} {{ dropdown_item('Edit', onclick='edit(item)', icon='pencil') }} {{ dropdown_divider() }} {{ dropdown_item('Delete', onclick='confirmDelete(item)', icon='trash', variant='danger') }} {% endcall %} ``` ### dropdown_item Menu item. ```jinja {{ dropdown_item('Edit', onclick='edit()', icon='pencil') }} {{ dropdown_item('View Details', href='/details/123', icon='eye') }} {{ dropdown_item('Delete', onclick='delete()', icon='trash', variant='danger') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Item label | | `onclick` | string | `none` | Click handler | | `href` | string | `none` | Link URL | | `icon` | string | `none` | Icon name | | `variant` | string | `'default'` | `'default'`, `'danger'` | | `disabled` | bool | `false` | Disabled state | ### select_dropdown Custom select dropdown. ```jinja {% call select_dropdown(placeholder='Select status...') %} {{ select_option('active', 'Active') }} {{ select_option('inactive', 'Inactive') }} {{ select_option('pending', 'Pending') }} {% endcall %} ``` --- ## Forms **File:** `shared/macros/forms.html` ```jinja {% from 'shared/macros/forms.html' import form_input, form_select, form_textarea, form_checkbox, form_toggle, password_input, search_input %} ``` ### form_input Standard text input. ```jinja {{ form_input('Email', 'email', 'formData.email', type='email', required=true, placeholder='Enter email') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `label` | string | required | Field label | | `name` | string | required | Input name | | `x_model` | string | required | Alpine.js model | | `type` | string | `'text'` | Input type | | `placeholder` | string | `''` | Placeholder | | `required` | bool | `false` | Required | | `disabled` | string | `none` | Alpine.js disabled condition | | `error` | string | `none` | Alpine.js error message | | `help` | string | `none` | Help text | | `maxlength` | int | `none` | Max length | ### password_input Password input with show/hide toggle. ```jinja {{ password_input('Password', 'formData.password', required=true, show_strength=true) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `show_strength` | bool | `false` | Show strength indicator | | `minlength` | int | `none` | Minimum length | ### input_with_icon Input with icon. ```jinja {{ input_with_icon('Website', 'formData.url', 'url', icon='globe', placeholder='https://...') }} {{ input_with_icon('Search', 'query', 'q', icon='search', on_click_icon='search()') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `icon` | string | required | Icon name | | `icon_position` | string | `'left'` | `'left'`, `'right'` | | `on_click_icon` | string | `none` | Makes icon clickable | ### form_select Select dropdown. ```jinja {{ form_select('Status', 'formData.status', [ {'value': 'active', 'label': 'Active'}, {'value': 'inactive', 'label': 'Inactive'} ], required=true) }} ``` ### form_select_dynamic Select with Alpine.js options. ```jinja {{ form_select_dynamic('Category', 'formData.categoryId', 'categories', value_key='id', label_key='name') }} ``` ### form_textarea Textarea input. ```jinja {{ form_textarea('Description', 'formData.description', rows=4, maxlength=500) }} ``` ### form_checkbox Checkbox input. ```jinja {{ form_checkbox('Subscribe to newsletter', 'formData.subscribe', help='We will send weekly updates') }} ``` ### form_toggle Toggle switch. ```jinja {{ form_toggle('Enable notifications', 'formData.notifications') }} ``` ### form_radio_group Radio button group. ```jinja {{ form_radio_group('Priority', 'priority', 'formData.priority', [ {'value': 'low', 'label': 'Low'}, {'value': 'medium', 'label': 'Medium'}, {'value': 'high', 'label': 'High'} ], inline=true) }} ``` ### search_input Search input with icon. ```jinja {{ search_input(x_model='filters.search', placeholder='Search items...', on_input='debouncedSearch()') }} ``` ### filter_select Compact filter select (no label). ```jinja {{ filter_select(x_model='filters.status', options=[ {'value': 'active', 'label': 'Active'}, {'value': 'inactive', 'label': 'Inactive'} ], placeholder='All Statuses', on_change='applyFilters()') }} ``` ### file_input Drag and drop file upload. ```jinja {{ file_input('Upload Image', 'image', accept='image/*', max_size=5, on_change='handleFileSelect($event)') }} ``` --- ## Headers **File:** `shared/macros/headers.html` ```jinja {% from 'shared/macros/headers.html' import page_header, section_header, breadcrumbs, tab_header %} ``` ### page_header Page title with optional action button. ```jinja {{ page_header('User Management', subtitle='Manage all users', action_label='Add User', action_url='/admin/users/create', action_icon='plus') }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `title` | string | required | Page title | | `subtitle` | string | `none` | Subtitle | | `action_label` | string | `none` | Action button text | | `action_url` | string | `none` | Action button URL | | `action_onclick` | string | `none` | Action click handler | | `action_icon` | string | `'plus'` | Action button icon | | `back_url` | string | `none` | Back button URL | ### page_header_dynamic Page header with Alpine.js title. ```jinja {{ page_header_dynamic(title_var='pageTitle', back_url='/admin/items') }} ``` ### section_header Section header within a page. ```jinja {{ section_header('Account Settings', subtitle='Update your account preferences', icon='cog', action_label='Edit', action_onclick='openSettings()') }} ``` ### breadcrumbs Navigation breadcrumbs. ```jinja {{ breadcrumbs([ {'label': 'Home', 'url': '/admin'}, {'label': 'Users', 'url': '/admin/users'}, {'label': 'John Doe'} ]) }} ``` ### tab_header Tab navigation. ```jinja {{ tab_header([ {'id': 'general', 'label': 'General', 'icon': 'cog'}, {'id': 'security', 'label': 'Security', 'icon': 'shield-check'}, {'id': 'notifications', 'label': 'Notifications', 'icon': 'bell'} ], active_var='activeTab') }} ``` --- ## Modals **File:** `shared/macros/modals.html` ```jinja {% from 'shared/macros/modals.html' import modal, confirm_modal, form_modal, slide_over %} ``` ### modal Basic modal dialog. ```jinja {% call modal('editModal', 'Edit Item', show_var='isEditModalOpen', size='lg') %}Modal content here
{% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `id` | string | required | Modal ID | | `title` | string | required | Modal title | | `show_var` | string | `'isModalOpen'` | Alpine.js visibility variable | | `size` | string | `'md'` | `'sm'`, `'md'`, `'lg'`, `'xl'`, `'full'` | | `show_close` | bool | `true` | Show close button | | `show_footer` | bool | `true` | Show footer | | `close_on_backdrop` | bool | `true` | Close on backdrop click | | `close_on_escape` | bool | `true` | Close on Escape key | ### confirm_modal Confirmation dialog for destructive actions. ```jinja {{ confirm_modal( 'deleteModal', 'Delete User', 'Are you sure you want to delete this user? This action cannot be undone.', 'deleteUser()', 'isDeleteModalOpen', confirm_text='Delete', variant='danger' ) }} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `confirm_action` | string | required | Alpine.js action on confirm | | `confirm_text` | string | `'Confirm'` | Confirm button text | | `cancel_text` | string | `'Cancel'` | Cancel button text | | `variant` | string | `'danger'` | `'danger'`, `'warning'`, `'info'` | ### form_modal Modal optimized for forms with loading state. ```jinja {% call form_modal('createModal', 'Create Item', submit_action='createItem()', loading_var='saving') %} {{ form_input('Name', 'name', 'formData.name', required=true) }} {{ form_textarea('Description', 'formData.description') }} {% endcall %} ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `submit_action` | string | `'submitForm()'` | Form submit handler | | `submit_text` | string | `'Save'` | Submit button text | | `loading_var` | string | `'saving'` | Loading state variable | | `loading_text` | string | `'Saving...'` | Loading button text | ### slide_over Side panel that slides in from the right. ```jinja {% call slide_over('detailsPanel', 'Item Details', show_var='isPanelOpen', width='lg') %}Panel content here
Subtitle
150
Imported
|
Field Name
|
Value |