uptime · 1404 days · 23 posts published · last deploy 1 day, 10 hours ago build:passing rss
~ / software-web / claude-code-template-handoff-vom-design-zum-wagt.md
Software & Web · 03. Juni 2026 · ~12min · a701550

Claude Code Template-Handoff: Vom Design zum Wagtail-Layout

hero-grid, code-frame & responsive Partials – was die KI baute und was ich nachrüsten musste

>
devmaker.net
author · a701550 · 2026-06-03
x
Claude Code Template-Handoff Hero.jpg 1024×1024
Claude Code Template-Handoff Hero
Design-Handoff: vom statischen claude.ai/design-Prototype zum Wagtail-Template.
Im ersten Teil habe ich gezeigt, wie ich mit claude.ai/design in einem Satz drei Blog-Designs bekommen habe. Was dort nach „einfach einbauen“ aussah, ist in Wahrheit ein Handoff mit Lücken: Der Prototype ist statisches HTML mit Platzhalterdaten, kein Django-Template. Dieser Artikel zeigt am echten Terminal-Editorial-Layout, wie Claude Code die Lücke schließt – vom hero-grid mit grid-template-areas über den code-frame bis zu den Model-Properties, die das Design stillschweigend voraussetzt. Du brauchst Django/Wagtail-Grundlagen, am Ende verstehst du, wo bei einem KI-Design-Handoff wirklich die Arbeit steckt.

Im ersten Teil ging es um das Ergebnis: ein Satz Prompt, drei Designs, eines davon („Terminal Editorial“) ist jetzt diese Seite. Dieser Teil geht eine Ebene tiefer – in den Code-Handoff zwischen claude.ai/design und Claude Code. Denn genau da entscheidet sich, ob aus einem hübschen Prototype ein wartbares Template wird.

Was Claude Design liefert – und was nicht

claude.ai/design gibt dir einen statischen Hi-Fi-Prototype: HTML mit Tailwind-Klassen, daisyUI-Komponenten und hartkodierten Platzhalterdaten. Das sieht im Browser fertig aus – ist aber kein Django-Template. Es fehlt alles, was ein CMS ausmacht: {% extends %}, {% for %}-Schleifen, {{ page.field }}-Variablen, i18n, und vor allem die Brücke zu deinem Datenmodell.

Der Handoff ist also kein Copy-Paste, sondern eine Übersetzung: vom Mockup mit Fake-Daten zum Template, das echte Wagtail-Pages rendert. Genau diese Übersetzung hat Claude Code übernommen.

Das hero-grid: Mobile-First mit grid-template-areas

Das auffälligste Element ist der Hero. Im Prototype war die Reihenfolge auf dem Desktop fix: Text links, Bild rechts. Auf dem Handy muss aber eine andere Reihenfolge her – Titel zuerst, dann Bild, dann Beschreibung und Call-to-Action. Statt zwei getrennter Markup-Blöcke löst grid-template-areas das mit einem Markup und zwei Layouts:

{# Mobil: Titel → Bild → Dek/Tags/CTA.
   Desktop (lg): Text links, Bild rechts (grid-template-areas). #}
<section class="hero-grid px-4 sm:px-6 lg:px-10 pt-10 pb-10">
  <div class="hero-head min-w-0" data-cat="{{ hero.category.slug }}">
    ...
    <h1 class="display-title text-[clamp(2.25rem,4vw,3.5rem)]">
      <a href="{{ hero.get_absolute_url }}">{{ hero.title }}</a>
    </h1>
  </div>
  <div class="hero-rest min-w-0"> ... Dek, Tags, CTA ... </div>
  <div class="hero-media min-w-0"> ... Bild oder code-frame ... </div>
</section>
/* Mobil: alles untereinander, Bild zwischen Kopf und Rest */
.hero-grid {
  display: grid;
  grid-template-areas: "head" "media" "rest";
  gap: 1.5rem;
}
.hero-head  { grid-area: head; }
.hero-media { grid-area: media; }
.hero-rest  { grid-area: rest; }

/* Desktop: zwei Spalten, Text links, Bild rechts über beide Zeilen */
@media (min-width: 1024px) {
  .hero-grid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "head media"
      "rest media";
    gap: 2.5rem 3rem;
  }
}

Der Clou: Die DOM-Reihenfolge bleibt gleich (gut für Screenreader und Tab-Order), nur die visuelle Anordnung kippt am Breakpoint. clamp() in der Titelgröße spart zusätzlich ein halbes Dutzend Tailwind-Breakpoint-Klassen – die Schrift skaliert fließend zwischen 2,25rem und 3,5rem.

kicker-prompt & code-frame: Terminal-Ästhetik per CSS

Das Design lebt von der Terminal-Metapher. Über jedem Hero steht ein Fake-Shell-Prompt mit blinkendem Cursor, der den Slug der Seite als Dateinamen zeigt:

<div class="kicker-prompt mb-4 flex items-center gap-2.5 flex-wrap">
  <span>cat featured/</span>
  <span class="text-base-content break-all">{{ hero.slug }}.md</span>
  <span class="cursor"></span>
</div>

Der Cursor ist reines CSS – kein JavaScript, keine Animation-Library:

.cursor {
  display: inline-block;
  width: 0.6ch;
  height: 1.1em;
  background: var(--p); /* daisyUI primary */
  animation: blink 1.1s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }

Spannender ist der code-frame: Hat ein Artikel kein Hero-Bild, rendert das Template stattdessen einen Fake-Editor mit Ampel-Punkten, Zeilennummern und einem Code-Auszug. Das ist der Fallback, der die Startseite auch ohne ein einziges Bild stimmig hält:

{% elif hero.hero_code %}
<figure class="code-frame">
  <header class="code-frame__bar">
    <span class="dot dot--r"></span>
    <span class="dot dot--y"></span>
    <span class="dot dot--g"></span>
    <span class="path">~/{{ hero.slug }}/main.py</span>
  </header>
  <div class="code-frame__body">
    <div class="code-frame__lines">
      {% for n in hero.hero_code_lines %}<div>{{ n }}</div>{% endfor %}
    </div>
    <div><pre class="m-0">{{ hero.hero_code }}</pre></div>
  </div>
</figure>

Der eigentliche Aufwand: Model-Properties nachrüsten

Hier steckt die Arbeit, die im Prototype unsichtbar ist. Das Design verwendet selbstverständlich Variablen wie hero.dek, hero.read_minutes, hero.commit_hash, hero.comment_count und hero.hero_code_lines – im Mockup waren das Platzhalter. In Wagtail existieren diese Felder schlicht nicht. Damit das Template nicht beim ersten Render mit VariableDoesNotExist kippt, mussten sie ins Model. Manche als echtes Feld, die meisten aber als berechnete @property:

import re
from django.utils.functional import cached_property

class ArticlePage(Page):
    # ... bestehende Felder ...

    @cached_property
    def read_minutes(self):
        """Grobe Lesezeit: ~200 Wörter/Minute über alle Textblöcke."""
        words = 0
        for block in self.body:
            text = str(getattr(block.value, "source", block.value))
            words += len(re.sub(r"<[^>]+>", " ", text).split())
        return max(1, round(words / 200))

    @property
    def commit_hash(self):
        """Dekorativer 'Commit' – stabil pro Page, rein kosmetisch."""
        return f"#{self.id:07x}"

    @property
    def dek(self):
        """Vorhandene Felder wiederverwenden statt neues Feld erfinden."""
        return self.subtitle or self.search_description or self.introduction

Das ist die ehrliche Lektion: Ein KI-Design erfindet das Vokabular, das es gerne hätte. Die Kunst beim Handoff ist, dieses Vokabular auf vorhandene Daten zu mappen, statt blind neue Felder anzulegen. dek ist einfach mein Untertitel, commit_hash ist Kosmetik aus der ID, read_minutes ist berechnet. Kein einziges neues Migrations-Feld, wo eine Property reicht.

Ein KI-Design beschreibt, wie deine Daten aussehen sollten. Der Handoff ist die Stelle, an der du entscheidest, ob du das Modell biegst oder die Property baust.

Responsive Partials statt Copy-Paste

Der Prototype wiederholte das Karten-Markup für jeden Artikel inline. Claude Code hat das in wiederverwendbare Includes zerlegt – partials/v1/card.html für die Featured-Reihe, row.html für die kompakte Liste älterer Posts, sidebar.html für die Tag-Cloud. Die Startseite ruft sie nur noch in Schleifen auf:

{# Featured: genau eine Reihe, max. 3 Artikel #}
<section class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
  {% for post in featured|slice:":3" %}
    {% include "partials/v1/card.html" %}
  {% endfor %}
</section>

{# Ältere Posts als kompakte Zeilen + Sidebar #}
<section class="grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-10">
  <div>
    {% for post in latest %}{% include "partials/v1/row.html" %}
    {% empty %}<p>$ ls posts/ — no matches</p>{% endfor %}
  </div>
  {% include "partials/v1/sidebar.html" %}
</section>

So sieht das fertige Terminal-Editorial-Layout dann live aus – Hero links, code-frame/Bild rechts, darunter die Featured-Reihe:

design-terminal-v1.jpg 1346×1121
design-terminal-v1
Das umgesetzte Terminal-Editorial-Layout (V1): Hero mit kicker-prompt links, Media rechts, darunter die Featured-Karten.

i18n nicht vergessen

Ein statischer Prototype kennt keine Sprachen. devmaker.net ist aber zweisprachig (DE/EN). Jeder feste String im Template – „latest writeups“, „older posts“ – musste durch {% translate %} ersetzt werden, sonst hätte die englische Seite deutsche Section-Header gezeigt:

{% load i18n %}
<div class="...">
  <span class="text-primary">~/posts</span>
  <span>/</span>
  <span>{% translate "latest writeups" %}</span>
</div>
...
<div>// {% translate "older posts — sorted by date desc" %}</div>

Lessons Learned

  • Der Prototype ist eine Spezifikation, kein Produkt. Behandle ihn wie ein detailliertes Pflichtenheft in HTML – nicht wie fertigen Code.
  • Die KI erfindet Datenfelder. Plane Zeit ein, ihr Wunsch-Vokabular auf dein echtes Model zu mappen. Properties vor Migrations.
  • Fallbacks sind Gold wert. Der code-frame für bildlose Artikel war Claude Designs Idee – und genau das, was eine Tech-Startseite ohne Stockfoto-Zwang rettet.
  • Was ich weggelassen habe: Der Prototype hatte eine animierte Tag-Filter-Leiste mit JS. Bewusst rausgeworfen – nicht überengineered, der Mehrwert war zu klein für die Komplexität.

Fazit

Der „Magie“-Moment ist nicht das Design, sondern der Handoff. claude.ai/design liefert die Vision, Claude Code macht daraus ein Template, das mit echten Daten, zwei Sprachen und fehlenden Bildern klarkommt. Der Aufwand verschiebt sich vom Pixel-Schieben zum sauberen Mapping zwischen Design-Annahmen und Datenmodell – und genau das ist die Arbeit, die ein Mockup nie zeigt.

// responses (0)
> echo "your thoughts" >> claude-code-template-handoff-vom-design-zum-wagt.responses

Schreibe einen Kommentar

Wird für die Bestätigung benötigt