11 KiB
11 KiB
Template Structure and Conventions - Public Web Frontend
Last Updated: November 18, 2025
Overview
This document outlines the template structure, naming conventions, and best practices for Jinja2 templates in the Code of Conquest web frontend.
Template Philosophy:
- Clean, semantic HTML
- Separation of concerns (templates, styles, scripts)
- Reusable components via includes and macros
- Responsive design patterns
- Accessibility-first
Directory Structure
templates/
├── base.html # Base template (all pages extend this)
├── errors/ # Error pages
│ ├── 404.html
│ ├── 500.html
│ └── 403.html
├── auth/ # Authentication pages
│ ├── login.html
│ ├── register.html
│ └── forgot_password.html
├── dashboard/ # User dashboard
│ └── index.html
├── characters/ # Character management
│ ├── list.html
│ ├── create.html
│ ├── view.html
│ └── edit.html
├── sessions/ # Game sessions
│ ├── create.html
│ ├── active.html
│ ├── history.html
│ └── view.html
├── multiplayer/ # Multiplayer sessions
│ ├── create.html
│ ├── lobby.html
│ ├── session.html
│ └── complete.html
├── partials/ # Reusable partial templates
│ ├── navigation.html
│ ├── footer.html
│ ├── character_card.html
│ ├── combat_ui.html
│ └── session_summary.html
├── components/ # Reusable UI components (macros)
│ ├── forms.html
│ ├── buttons.html
│ ├── alerts.html
│ └── modals.html
└── macros/ # Jinja2 macros
├── form_fields.html
└── ui_elements.html
Base Template
File: templates/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Code of Conquest{% endblock %}</title>
<!-- CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% block extra_css %}{% endblock %}
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- Appwrite SDK (for realtime) -->
<script src="https://cdn.jsdelivr.net/npm/appwrite@latest"></script>
{% block extra_head %}{% endblock %}
</head>
<body>
{% include 'partials/navigation.html' %}
<main class="container">
{% block flash_messages %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="alerts">
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% endblock %}
{% block content %}
<!-- Page content goes here -->
{% endblock %}
</main>
{% include 'partials/footer.html' %}
<!-- JavaScript -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
Template Naming Conventions
File Names
- Use lowercase with underscores:
character_list.html,session_create.html - Partial templates prefix with underscore:
_card.html,_form.html(optional) - Component files describe what they contain:
forms.html,buttons.html
Template Variables
- Use snake_case:
character,session_data,user_info - Prefix collections with descriptive names:
characters_list,sessions_active - Boolean flags use
is_orhas_prefix:is_authenticated,has_premium
Block Names
- Use descriptive names:
{% block sidebar %},{% block page_header %} - Common blocks:
title- Page titlecontent- Main content areaextra_css- Additional CSS filesextra_js- Additional JavaScript filesextra_head- Additional head elements
Template Patterns
Extending Base Template
{% extends "base.html" %}
{% block title %}Character List - Code of Conquest{% endblock %}
{% block content %}
<div class="character-list">
<h1>Your Characters</h1>
<!-- Content -->
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/characters.js') }}"></script>
{% endblock %}
Including Partial Templates
{% include 'partials/character_card.html' with character=char %}
Or without context:
{% include 'partials/navigation.html' %}
Using Macros
Define macro in templates/macros/form_fields.html:
{% macro text_input(name, label, value="", required=False, placeholder="") %}
<div class="form-group">
<label for="{{ name }}">{{ label }}{% if required %} <span class="required">*</span>{% endif %}</label>
<input type="text"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
placeholder="{{ placeholder }}"
{% if required %}required{% endif %}>
</div>
{% endmacro %}
Use macro:
{% from 'macros/form_fields.html' import text_input %}
<form>
{{ text_input('character_name', 'Character Name', required=True, placeholder='Enter name') }}
</form>
Conditional Rendering
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>
{% endif %}
Loops
<div class="character-grid">
{% for character in characters %}
<div class="character-card">
<h3>{{ character.name }}</h3>
<p>Level {{ character.level }} {{ character.player_class.name }}</p>
</div>
{% else %}
<p>No characters found. <a href="{{ url_for('characters.create') }}">Create one</a>?</p>
{% endfor %}
</div>
HTMX Integration in Templates
Basic HTMX Attributes
<!-- Form submission via HTMX -->
<form hx-post="{{ url_for('characters.create') }}"
hx-target="#character-list"
hx-swap="beforeend">
<!-- form fields -->
<button type="submit">Create Character</button>
</form>
HTMX with Confirmation
<button hx-delete="{{ url_for('characters.delete', character_id=char.character_id) }}"
hx-confirm="Are you sure you want to delete this character?"
hx-target="closest .character-card"
hx-swap="outerHTML">
Delete
</button>
HTMX Polling
<div hx-get="{{ url_for('sessions.status', session_id=session.session_id) }}"
hx-trigger="every 5s"
hx-target="#session-status">
Loading...
</div>
Component Patterns
Character Card Component
File: templates/partials/character_card.html
<div class="character-card" data-character-id="{{ character.character_id }}">
<div class="card-header">
<h3>{{ character.name }}</h3>
<span class="level-badge">Lvl {{ character.level }}</span>
</div>
<div class="card-body">
<p class="class">{{ character.player_class.name }}</p>
<p class="stats">
HP: {{ character.current_hp }}/{{ character.max_hp }} |
Gold: {{ character.gold }}
</p>
</div>
<div class="card-actions">
<a href="{{ url_for('characters.view', character_id=character.character_id) }}" class="btn btn-primary">View</a>
<a href="{{ url_for('sessions.create', character_id=character.character_id) }}" class="btn btn-secondary">Play</a>
</div>
</div>
Usage:
{% for character in characters %}
{% include 'partials/character_card.html' with character=character %}
{% endfor %}
Alert Component Macro
File: templates/components/alerts.html
{% macro alert(message, category='info', dismissible=True) %}
<div class="alert alert-{{ category }}{% if dismissible %} alert-dismissible{% endif %}" role="alert">
{{ message }}
{% if dismissible %}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{% endif %}
</div>
{% endmacro %}
Usage:
{% from 'components/alerts.html' import alert %}
{{ alert('Character created successfully!', 'success') }}
{{ alert('Invalid character name', 'error') }}
Accessibility Guidelines
Semantic HTML
- Use proper heading hierarchy (
<h1>,<h2>, etc.) - Use
<nav>,<main>,<article>,<section>elements - Use
<button>for actions,<a>for navigation
ARIA Labels
<button aria-label="Delete character">
<span class="icon icon-trash"></span>
</button>
<nav aria-label="Main navigation">
<!-- navigation links -->
</nav>
Form Labels
<label for="character-name">Character Name</label>
<input type="text" id="character-name" name="character_name">
Focus Management
<button class="btn" autofocus>Primary Action</button>
Best Practices
1. Keep Templates DRY (Don't Repeat Yourself)
- Use includes for repeated sections
- Create macros for reusable components
- Extend base template consistently
2. Separate Logic from Presentation
- Avoid complex Python logic in templates
- Use template filters instead of inline calculations
- Pass fully-formed data from views
3. Use Template Comments
{# This is a Jinja2 comment, not rendered in HTML #}
<!-- This is an HTML comment, visible in source -->
4. Whitespace Control
{% for item in items -%}
<li>{{ item }}</li>
{%- endfor %}
5. Template Inheritance Hierarchy
base.html
├── dashboard/base.html (extends base.html)
│ ├── dashboard/index.html
│ └── dashboard/profile.html
└── sessions/base.html (extends base.html)
├── sessions/create.html
└── sessions/active.html
Testing Templates
Manual Checklist
- Templates extend base.html correctly
- All blocks are properly closed
- Variables are defined before use
- Forms have CSRF protection
- Links use
url_for()instead of hardcoded paths - Images have alt text
- Buttons have descriptive text or aria-labels
- Mobile responsive (test at 320px, 768px, 1024px)
Template Linting
- Use
djLintfor Jinja2 template linting - Ensure consistent indentation (2 or 4 spaces)
- Validate HTML with W3C validator
Related Documentation
- HTMX_PATTERNS.md - HTMX integration patterns
- TESTING.md - Manual testing guide
- /api/docs/API_REFERENCE.md - API endpoints for HTMX calls
Document Version: 1.0 Created: November 18, 2025 Last Updated: November 18, 2025