Files
2025-11-24 23:10:55 -06:00

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_ or has_ prefix: is_authenticated, has_premium

Block Names

  • Use descriptive names: {% block sidebar %}, {% block page_header %}
  • Common blocks:
    • title - Page title
    • content - Main content area
    • extra_css - Additional CSS files
    • extra_js - Additional JavaScript files
    • extra_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">&times;</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 djLint for Jinja2 template linting
  • Ensure consistent indentation (2 or 4 spaces)
  • Validate HTML with W3C validator


Document Version: 1.0 Created: November 18, 2025 Last Updated: November 18, 2025