# Jinja2 / FastAPI — Referenz
Referenz für den Einsatz von CSS-Framework in FastAPI-Apps mit Jinja2-Templates und HTMX.
Schnellreferenz für Komponenten und Tokens: [SKILL.md](../SKILL.md)
---
## Setup
```python
# main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="...")
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
```
Statische Dateien werden direkt über den Pfad `/static/...` referenziert — kein `url_for` im Template nötig.
---
## Basis-Template
`templates/base.html`:
```html
{% block title %}App{% endblock %}
{% block head %}{% endblock %}
{% block mobile_title %}App{% endblock %}
{% for msg in request.session.pop('_messages', []) %}
{{ msg.text }}
{% endfor %}
{% block content %}{% endblock %}
{% block scripts %}{% endblock %}
```
---
## Template rendern
```python
from fastapi import Request
from fastapi.responses import HTMLResponse
@router.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse(request, "index.html", {
"items": await get_items()
})
```
`request` wird immer explizit übergeben — Jinja2Templates macht ihn im Template als `request` verfügbar.
---
## Flash Messages
FastAPI hat kein eingebautes Flash-System. Einfache Session-Lösung mit `SessionMiddleware`:
```python
# Helfer-Funktion
def flash(request: Request, text: str, category: str = "info"):
messages = request.session.setdefault('_messages', [])
messages.append({'text': text, 'category': category})
# In einer Route
@router.post("/profil")
async def profil_speichern(request: Request):
flash(request, 'Profil aktualisiert', 'success')
flash(request, 'Ungültige Eingabe', 'danger')
flash(request, 'Bitte bestätigen', 'warning')
return RedirectResponse("/profil", status_code=303)
```
Im Template werden die Messages über `request.session.pop('_messages', [])` gelesen (siehe Basis-Template oben).
---
## HTMX-Muster
### Button Loading State
`hx-disabled-elt="this"` sperrt den Button; `.htmx-indicator` blendet den Spinner ein solange die Anfrage läuft.
```html
```
### Formular mit Inline-Validation (422)
FastAPI antwortet bei Fehlern mit HTTP 422 und gibt das Formular-Partial zurück:
```python
from fastapi import Form, status
from fastapi.responses import HTMLResponse
@router.post("/api/formular", response_class=HTMLResponse)
async def formular(
request: Request,
email: str = Form(...)
):
errors = {}
if "@" not in email:
errors["email"] = "Ungültige E-Mail-Adresse"
if errors:
return templates.TemplateResponse(
request, "_form.html",
{"form": {"email": email}, "errors": errors},
status_code=422
)
# Erfolg
return templates.TemplateResponse(request, "_form_success.html", {})
```
```html
```
### Skeleton als Ladeindikator
```html
` brauchen keine Klassen
- `request` immer explizit an `TemplateResponse` übergeben — Starlette macht ihn im Template verfügbar
- HTMX-Attribute direkt auf Framework-Elemente — kein Wrapper-`
` nötig
- Globale JS-Funktionen nutzen (`openModal`, `showToast`, `sortTable` usw.)
**Don't:**
- Keine `style`-Attribute, keine `on…`-Attribute, kein Inline-`