01 / SPACING IST NICHT GLEICH RHYTHMUS · 3 MINUTEN
Gleiche Inhalte, andere Beziehungen.
Ein Designsystem mit 4 / 8 / 16 / 24 / 32 px garantiert noch keine gute Hierarchie. Nähe soll Zugehörigkeit vermitteln: Label und Wert bilden eine Einheit; zwei unabhängige Kennzahlen brauchen eine erkennbare Trennung.
Wechsle im Labor zwischen kompakt und großzügig. Inhalte, Farben und Schriftgrößen bleiben gleich; nur Abstände und Innenräume ändern sich. Welche Variante würdest du für einen Überblick wählen? Welche für ein Monitoring mit zwölf gleichzeitig benötigten Karten?
Identische Inhalte und Schriftgrößen. Nur die Abstände wechseln. Alle Werte sind fiktiv.
UMWELTMONITORING
CO₂-Bilanz
Emissionen
8.
+12,5 % zum Vormonat
Kompensation
6.
+8,2 % zum Vormonat
Abstandsebenen: innerhalb des Blocks 4 px · zwischen Kennzahlen 16 px · zwischen Bereichen 24 px. Die breitere Rastervariante der Musterlösung benötigt mindestens 900 px Komponentenbreite.
Großzügige Abstände können Beziehungen deutlicher trennen. Kompakte Abstände können mehr Informationen im sichtbaren Bereich halten. Beides ist eine Hypothese für eine konkrete Aufgabe, kein Qualitätsurteil allein aufgrund der Menge an Weißraum.
Die Beispielwerte sind unabhängig konstruierte Kennzahlen. „Kompensation“ wird hier nicht von Emissionen abgezogen, und ein positives Vorzeichen ist keine Bewertung. Die semantische Einordnung einer Kennzahl hast du in Lektion 6 betrachtet.
02 / DREI ABSTANDSEBENEN
Nicht alles im gleichen Verhältnis skalieren.
- Innerhalb eines Blocks: Ein kleiner Abstand verbindet Label, Wert und zugehörige Veränderung.
- Zwischen Kennzahlen: Ein größerer Abstand trennt eigenständige Informationsgruppen.
- Zwischen Bereichen: Noch mehr Raum trennt etwa Dashboard-Überschrift, Kennzahlenraster und erklärende Hinweise.
Wenn du alle Abstände gleichmäßig vergrößerst oder verkleinerst, werden diese Beziehungen nicht automatisch klarer. In der Musterlösung wechselt die kleine Ebene von 4 auf 8 px, die mittlere von 16 auf 24 px und die große von 24 auf 32 px. Die Unterschiede folgen keinem gemeinsamen Skalierungsfaktor.
Ein operatives Dashboard darf dicht sein, wenn Menschen Kennzahlen sicher erfassen und vergleichen können. Eine ruhigere Übersichtsseite kann andere Schwerpunkte setzen. Prüfe Blickführung, Beschriftungen, Zahlenalignment und Inhalt gemeinsam mit den Abständen.
03 / DEINE CHALLENGE · 8 MINUTEN
Ein responsives KPI-System gestalten.
Entwirf in Figma oder Code ein Dashboard mit vier Kennzahlen: Emissionen, Kompensation, Energieverbrauch und Kosten. Es soll auf Desktop und in einer mobilen Ansicht bei 320 px funktionieren.
- Hierarchie · 2 Minuten: Definiere drei Abstandsebenen. Begründe, welche Elemente zusammengehören und welche voneinander getrennt werden.
- Layout · 4 Minuten: Gestalte eine breite und eine schmale Variante. Entscheide, wann Kennzahlen nebeneinander stehen dürfen. Reduziere nicht einfach alle Abstände proportional.
- Belastungstest · 2 Minuten: Prüfe „Gesamte kompensierte Treibhausgasemissionen“ und „1.284.590,75 kg“. Verdopple zusätzlich die Textgröße.
Keine festen Kartenhöhen, abgeschnittenen Einheiten oder überlappenden Elemente. Eine größere Schrift darf mehr Höhe beanspruchen. Begründe zwei Entscheidungen und einen Nachteil deiner Dichtewahl. Prüfe Vergrößerung zusätzlich im Browser oder mit den Texteinstellungen des Geräts.
04 / EINE MÖGLICHE MUSTERLÖSUNG
Der verfügbare Raum bestimmt das Raster.
Responsives Dashboard mit Belastungstest öffnen
Vier fiktive Kennzahlen. Die Zielbreite ist auf den tatsächlich verfügbaren Platz begrenzt. Die Textoption vergrößert nur das Beispiel und ersetzt keinen Test mit Browserzoom oder Geräteeinstellungen.
UMWELTMONITORING
Ressourcen im Überblick
Emissionen
8.
+12,5 % zum Vormonat
Kompensation
6.
+8,2 % zum Vormonat
Energieverbrauch
42.
−3,1 % zum Vormonat
Kosten
12.
+2,4 % zum Vormonat
Abstandsebenen: innerhalb des Blocks 4 px · zwischen Kennzahlen 16 px · zwischen Bereichen 24 px. Die breitere Rastervariante der Musterlösung benötigt mindestens 900 px Komponentenbreite.
So prüfst du die Lösung
- Nutze auf einem breiten Bildschirm den gesamten verfügbaren Platz. Ab 900 px Komponentenbreite erscheinen vier Kennzahlen nebeneinander.
- Wechsle zu 560 px und 320 px. Du siehst zwei beziehungsweise eine Spalte, sofern der gewählte Platz tatsächlich verfügbar ist. Auf einer schmalen Seite wird die Zielbreite entsprechend begrenzt.
- Aktiviere lange Bezeichnung und großen Wert. Zahl und Einheit bleiben vollständig vorhanden. Prüfe auch die Kombination mit 200 % Text.
- Vergleiche beide Dichten unter denselben Bedingungen. Was wird schneller erfassbar, was benötigt mehr Scrollen? Die Vorschau liefert keine gemessenen Ergebnisse.
Warum diese Entscheidungen?
- Beziehungen statt zufälliger Werte: Drei benannte Abstandsebenen steuern Nähe, Gruppentrennung und Bereichswechsel. Karteninnenabstände sind ebenfalls bewusst gewählt.
- Ein Raster nach Komponentenbreite: Container Queries schalten bei 560 und 900 px um. Diese Schwellen sind Ausgangsentscheidungen für das Beispiel. Eine schmale Sidebar bleibt auch auf einem großen Monitor ein schmaler Container.
- Schrift bleibt vergrößerbar: Relative Schriftgrößen bilden die Grundlage. Für die Zahlen kombiniert
clamp()eine relative Mindest- und Höchstgröße mit einem Containeranteil. Der Belastungstest muss zeigen, ob diese Kombination im verwendeten Schriftsystem funktioniert. - Lange Werte brauchen einen eigenen Umgang: Der lange Testwert erhält eine zurückhaltendere, weiterhin relative Zahlengröße. Bevor etwas abgeschnitten wird, darf er nach einer vollständigen Tausendergruppe umbrechen; „590,75“ bleibt zusammen. Die Einheit darf in eine eigene Zeile wechseln. Die Zahl bleibt im Text vollständig lesbar.
- Kein starres Höhenmaß: Lange Labels und große Schrift dürfen die Karten wachsen lassen. Gleiche Ziffernbreiten unterstützen den Zahlenvergleich; sie ersetzen keine sinnvolle Ausrichtung und Rasterwahl.
Der Nachteil: Ein mehrzeiliger Zahlenwert ist zwar vollständig sichtbar, kann aber schwerer zu vergleichen sein. Für häufig benötigte, sehr große Werte sollte ein echtes Produkt zusätzlich andere Gruppierungen oder eine klar erklärte Einheit wie Tonnen prüfen. Die Musterlösung nimmt keine stillschweigende Rundung oder Umrechnung vor.
Die Beispiele sind eigene Gestaltungsvorschläge ohne Nutzertest. Die Textoption vergrößert nur die Dashboard-Schrift; sie ist kein vollständiger Nachweis für Browserzoom, andere Schriften oder alle Barrierefreiheitsanforderungen.
React-Code und CSS der Musterlösung ansehen
Dies ist der tatsächliche Code beider Labore. Die einspaltige Grunddarstellung funktioniert auch ohne Container Queries; die Erweiterungen setzen entsprechende CSS-Unterstützung voraus.
coaching-spacing-lab.tsx
"use client";
import { Fragment, useId, useState, type CSSProperties } from "react";
import "./coaching-spacing-lab.css";
type Metric = { label: string; value: string; unit: string; change: string };
const metrics: Metric[] = [
{ label: "Emissionen", value: "8.540", unit: "kg", change: "+12,5 %" },
{ label: "Kompensation", value: "6.280", unit: "kg", change: "+8,2 %" },
{ label: "Energieverbrauch", value: "42.560", unit: "kWh", change: "−3,1 %" },
{ label: "Kosten", value: "12.840,50", unit: "€", change: "+2,4 %" },
];
function MetricValue({ value }: { value: string }) {
// Break only between complete thousands groups; the decimal group stays intact.
const groups = value.split(".");
return <>{groups.map((group, index) => <Fragment key={index}>{group}{index < groups.length - 1 && <>.<wbr /></>}</Fragment>)}</>;
}
export function CoachingSpacingLab({ solution = false }: { solution?: boolean }) {
const [density, setDensity] = useState<"compact" | "generous">("compact");
const [width, setWidth] = useState("auto");
const [stress, setStress] = useState(false);
const [large, setLarge] = useState(false);
const id = useId();
const rows = solution ? metrics.map((metric, index) => stress && index === 1 ? { ...metric, label: "Gesamte kompensierte Treibhausgasemissionen", value: "1.284.590,75" } : metric) : metrics.slice(0, 2);
const style = { width: width === "auto" ? "100%" : `${width}px` } as CSSProperties;
return <div className="spacing-lab">
<div className="spacing-controls"><div className="spacing-toggle" role="group" aria-label={solution ? "Dichte der Musterlösung" : "Dichte vergleichen"}><button type="button" aria-pressed={density === "compact"} onClick={() => setDensity("compact")}>Kompakt</button><button type="button" aria-pressed={density === "generous"} onClick={() => setDensity("generous")}>Großzügig</button></div>
{solution && <><div><label htmlFor={`${id}-width`}>Verfügbare Komponentenbreite</label><select id={`${id}-width`} value={width} onChange={event => setWidth(event.target.value)}><option value="auto">Gesamter verfügbarer Platz</option><option value="560">560 px</option><option value="320">320 px</option></select></div><label className="spacing-check"><input type="checkbox" checked={stress} onChange={event => setStress(event.target.checked)} /> Lange Bezeichnung & großer Wert</label><label className="spacing-check"><input type="checkbox" checked={large} onChange={event => setLarge(event.target.checked)} /> Text auf 200 %</label></>}
</div>
<p className="spacing-demo-note">{solution ? "Vier fiktive Kennzahlen. Die Zielbreite ist auf den tatsächlich verfügbaren Platz begrenzt. Die Textoption vergrößert nur das Beispiel und ersetzt keinen Test mit Browserzoom oder Geräteeinstellungen." : "Identische Inhalte und Schriftgrößen. Nur die Abstände wechseln. Alle Werte sind fiktiv."}</p>
<div className={`spacing-dashboard ${solution ? "is-dashboard" : "is-comparison"} density-${density}${large ? " has-large-text" : ""}${stress ? " has-stress-data" : ""}`} style={style}>
<div className="spacing-board"><header className="spacing-board-header"><p>UMWELTMONITORING</p><h3>{solution ? "Ressourcen im Überblick" : "CO₂-Bilanz"}</h3></header>
<div className="spacing-metrics-grid">{rows.map((metric, index) => <section className="spacing-metric" key={index} aria-labelledby={`${id}-metric-${index}`}><h4 id={`${id}-metric-${index}`}>{metric.label}</h4><p className={`spacing-metric-value${stress && index === 1 ? " is-long-value" : ""}`}><strong><MetricValue value={metric.value} /></strong>{" "}<span>{metric.unit}</span></p><p className="spacing-metric-change">{metric.change} <span>zum Vormonat</span></p></section>)}</div>
<footer className="spacing-board-footer">Vergleich zum Vormonat. Die Vorzeichen beschreiben Veränderungen, keine Bewertung der Zielerreichung.</footer>
</div>
</div>
<p className="spacing-demo-note">Abstandsebenen: innerhalb des Blocks {density === "compact" ? "4" : "8"} px · zwischen Kennzahlen {density === "compact" ? "16" : "24"} px · zwischen Bereichen {density === "compact" ? "24" : "32"} px. Die breitere Rastervariante der Musterlösung benötigt mindestens 900 px Komponentenbreite.</p>
</div>;
}
coaching-spacing-lab.css
.spacing-lab{max-width:100%;min-width:0;margin-top:24px}
.spacing-controls{display:flex;flex-wrap:wrap;align-items:center;gap:16px 24px;padding:20px;border:1px solid #c6d1e3;border-radius:10px;background:white}
.spacing-toggle{display:flex;gap:8px;flex-wrap:wrap}
.spacing-controls button,.spacing-controls select{min-height:44px;border:1px solid #758198;border-radius:6px;padding:10px 14px;background:white;font-size:13px;line-height:1.7;color:#263851;max-width:100%}
.spacing-controls button[aria-pressed=true]{background:#2558ed;border-color:#2558ed;color:white}
.spacing-controls>div:not(.spacing-toggle){min-width:0;max-width:100%}
.spacing-controls>div>label{display:block;font-size:12px;line-height:1.8;margin-bottom:8px}
.spacing-controls select{display:block;width:100%;min-width:0}
.spacing-check{display:flex;align-items:flex-start;gap:12px;font-size:14px;line-height:1.7;min-height:44px;padding:10px 0;cursor:pointer}
.spacing-check input{width:18px;height:18px;flex-shrink:0;accent-color:#2558ed;margin-top:3px}
.lesson-content p.spacing-demo-note{font-size:12px;line-height:1.8;color:#536174;margin-top:16px}
.spacing-dashboard{container-type:inline-size;container-name:coaching-metrics;max-width:100%;min-width:0;margin-top:24px;font:16px/1.6 system-ui,sans-serif;color:#1b2a42}
.spacing-dashboard.density-compact{--spacing-inner:4px;--spacing-group:16px;--spacing-area:24px;--spacing-card:16px}
.spacing-dashboard.density-generous{--spacing-inner:8px;--spacing-group:24px;--spacing-area:32px;--spacing-card:24px}
.spacing-dashboard.has-large-text{font-size:32px}
.spacing-board{min-width:0}
.spacing-board-header{margin-bottom:var(--spacing-area)}
.lesson-content .spacing-board-header p{font:400 .625em/1.8 monospace;letter-spacing:.08em;margin:0;color:#536174;overflow-wrap:anywhere}
.lesson-content .spacing-board-header h3{font:500 1.5em/1.4 system-ui,sans-serif;margin:var(--spacing-inner) 0 0;overflow-wrap:anywhere}
.spacing-metrics-grid{display:grid;grid-template-columns:1fr;gap:var(--spacing-group);min-width:0}
.spacing-metric{padding:var(--spacing-card);border:1px solid #bcc9df;border-radius:10px;background:#f0f4fb;min-width:0}
.lesson-content .spacing-metric h4{font:500 .875em/1.7 system-ui,sans-serif;margin:0;color:#44526a;overflow-wrap:anywhere}
.lesson-content .spacing-metric-value{display:flex;align-items:baseline;flex-wrap:wrap;column-gap:8px;row-gap:var(--spacing-inner);font-size:1em;line-height:1.4;margin:var(--spacing-inner) 0 0;color:#1b2a42;max-width:100%;font-variant-numeric:tabular-nums}
.spacing-metric-value strong{font-size:1.5em;font-size:clamp(1.25em,calc(1em + .35cqi),1.5em);font-weight:650;line-height:1.4;max-width:100%;overflow-wrap:anywhere}
.spacing-metric-value.is-long-value strong{font-size:1.125em}
.spacing-metric-value>span{font-size:1em;line-height:1.5;color:#44526a}
.lesson-content .spacing-metric-change{font-size:.75em;line-height:1.8;margin:var(--spacing-inner) 0 0;color:#44526a;font-variant-numeric:tabular-nums}
.spacing-metric-change span{display:inline-block}
.spacing-board-footer{font-size:.75em;line-height:1.8;color:#536174;margin-top:var(--spacing-area);overflow-wrap:anywhere}
.is-comparison .spacing-board{padding:var(--spacing-card);border:1px solid #c6d1e3;background:white;border-radius:12px;max-width:620px}
.is-comparison .spacing-metric{background:#f3f6fc}
.is-comparison .spacing-metric-value strong{font-size:1.75em}
.lesson-analysis-details.spacing-solution-details{max-width:960px}
.spacing-lab :is(button,input,select):focus-visible{outline:3px solid #2558ed;outline-offset:3px}
@container coaching-metrics (min-width:560px){.spacing-metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@container coaching-metrics (min-width:900px){.is-dashboard .spacing-metrics-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media(max-width:640px){.spacing-controls{padding:16px;gap:12px}.spacing-controls select{font-size:12px}}
05 / CONTAINER QUERIES UND TYPOGRAFIE
Ein Breakpoint ist eine überprüfbare Entscheidung.
Mit container-type: inline-size wird ein Element zum Größencontainer. Eine benannte @container-Abfrage kann dann das Layout seiner Nachfahren an dessen verfügbare Breite anpassen. Sie bezieht sich auf die Komponente, nicht pauschal auf den Viewport. cqi entspricht einem Prozent der Inline-Größe des maßgeblichen Query-Containers. MDN: CSS Container Queries.
Eine Container-Einheit allein macht Text nicht zuverlässig vergrößerbar. Prüfe relative Grenzen, lange Werte und das Umbruchverhalten gemeinsam. Vier Spalten sind keine Pflicht, nur weil eine bestimmte Breite erreicht wurde. Wenn die Inhalte nicht passen, muss sich das Raster oder die Darstellung ändern.
WCAG 1.4.4 beschreibt die Vergrößerung von Text bis 200 % ohne Verlust von Inhalt oder Funktion. Teste neben der eingebauten Hilfe auch einen unterstützten Vergrößerungsweg im Browser beziehungsweise auf dem Gerät. W3C: Resize Text.
06 / REFLEXION · 2 MINUTEN
Welche Beziehung fehlt deinem Layout?
Das Dashboard wirkt trotz konsistenter Tokens unruhig.
Vereinheitlichst du alles auf Vielfache von acht, vergrößerst du überall den Weißraum oder untersuchst du zuerst Gruppierung, Hierarchie und Gewichtung?
Mögliche Antwort zum visuellen Rhythmus
Ich prüfe zuerst, ob Label und Wert als Einheit erkennbar sind und ob wichtige Informationen ein angemessenes Gewicht erhalten. Danach betrachte ich die Trennung zwischen Gruppen und Bereichen sowie uneinheitliche Textlängen, Zeilenhöhen und Zahlenpositionen. Ein einheitliches Raster kann bei der Umsetzung helfen, erklärt aber noch keine inhaltlichen Beziehungen.
Ich verändere gezielt eine Ebene und vergleiche unter gleichen Bedingungen. Mehr Weißraum überall könnte gerade in einem Monitoring benötigte Informationen aus dem sichtbaren Bereich verdrängen.
Wann weichst du bewusst vom Spacing-System ab?
Nenne ein konkretes Beispiel und erkläre, wie du aus einer Ausnahme keine beliebige Sammlung von Sonderwerten machst.
Mögliche Antwort zu begründeten Ausnahmen
Eine kleine optische Korrektur an einer bestimmten Icon-Schrift-Kombination kann sinnvoll sein, nachdem ich ViewBox und Schriftmetrik geprüft habe. Die Ausnahme wird dokumentiert und in den relevanten Größen überprüft. Dafür verschiebe ich nicht sämtliche Abstände des Layouts.
Wenn eine Abweichung häufig nötig ist, prüfe ich das gemeinsame Muster: Vielleicht fehlt ein semantischer Token oder die Komponente braucht eine Variante. Für ein einzelnes Problem immer neue Werte einzuführen wäre schwer zu pflegen. Entscheidend ist die begründete Wirkung, nicht das bloße Einhalten oder Brechen des Rasters.
Dein Selbstcheck
- Erkennst du drei begründete Abstandsebenen?
- Bleiben Inhalte und Schrift beim Dichtevergleich identisch?
- Passt das Raster zur verfügbaren Komponentenbreite?
- Bleiben lange Zahlen und Einheiten vollständig sichtbar?
- Funktioniert Vergrößerung ohne feste Höhen und Überlappungen?
DEIN MERKSATZ
Konsistenz ist die Grundlage. Rhythmus braucht bewusste Unterschiede.
Ein Designsystem gibt dir Regeln. Gutes UI-Design verlangt, diese Regeln im Kontext der Nutzeraufgabe sinnvoll anzuwenden.