Zurück zum Blog
UI-Engineering

Moderne Dashboards mit Tailwind und TypeScript: Von der leeren Tabelle zum Entscheidungswerkzeug

Die meisten Dashboards sind schön und nutzlos. Was ich beim Bau des Admin-Panels für eine Commerce-Plattform gelernt habe — über die Reihenfolge von Zahlen, leere Zustände und klickbare Karten.

Mehran HatamiMehran Hatami6 Min. LesezeitTailwind CSSTypeScriptDashboards

Das erste Dashboard jedes Projekts sieht gleich aus: vier Kennzahlkarten oben, ein Diagramm, eine Tabelle. Der Ärger beginnt, wenn jemand den Gesamtumsatz sieht, ihn für falsch hält, darauf klickt — und nichts passiert. In diesem Moment hört das Dashboard auf, ein Werkzeug zu sein, und wird zum Poster.

Schreib die Entscheidung, bevor du die Komponente schreibst

Kernaussage

Jede Zahl auf einem Dashboard sollte dorthin führen, wo Nutzer etwas damit tun können. Eine Zahl ohne Ziel ist Dekoration.

Vor jedem Code schreibe ich diesen Satz: „Nutzer öffnen diese Seite, um zu entscheiden, ob …“. Wenn ich ihn nicht zu Ende bringe, ist die Seite noch nicht baureif. Für das Shop-Panel lautete die Antwort: „um zu sehen, was heute ihre Aufmerksamkeit braucht.“ Dieser eine Satz hat die gesamte Reihenfolge geändert — offene Bestellungen wanderten nach oben, das Jahresumsatz-Diagramm nach unten.

Eine Kennzahlkarte braucht ein Ziel

Das ist die einfachste Verbesserung für jedes Dashboard und zugleich die wirksamste: Mach jede Kennzahlkarte zu einem Link auf das, was sie gezählt hat. Eine Karte mit „12 offene Bestellungen“ sollte genau diese zwölf öffnen, Filter bereits gesetzt. Nutzer verstehen sofort, woher die Zahl kommt, und können sie prüfen, wenn sie seltsam wirkt.

export function StatCard({ label, value, href }: {
  label: string; value: string; href?: string;
}) {
  const inner = (
    <>
      <p className="text-sm text-foreground/60">{label}</p>
      <p className="mt-1 text-2xl font-bold tabular-nums">{value}</p>
    </>
  );

  if (!href) return <div className="rounded-card p-5">{inner}</div>;

  return (
    <Link
      href={href}
      className="rounded-card p-5 transition hover:bg-foreground/5
                 focus-visible:outline focus-visible:outline-2"
    >
      {inner}
    </Link>
  );
}

Ein kleines Detail mit großer Wirkung: tabular-nums. Ohne es zittern Zahlen bei jeder Aktualisierung, weil Ziffern unterschiedlich breit sind. Mit ihm steht die Spalte still und die Seite wirkt ruhiger.

Der leere Zustand ist der ernsteste Zustand

Jeder neue Nutzer sieht ihn zuerst. Eine leere Tabelle mit „Keine Daten vorhanden“ sagt: Das Produkt ist kaputt. Dieselbe Tabelle mit einem erklärenden Satz und einem Button „Erstes Produkt anlegen“ sagt, was als Nächstes zu tun ist. Leere Zustände gehören wie eine Anleitung gestaltet, nicht wie ein Fehler.

Dasselbe gilt fürs Laden. Ein Skeleton in der Form des späteren Inhalts schlägt einen Spinner in der Bildschirmmitte, weil das Auge das Layout vorab findet und beim Eintreffen der Daten nichts springt.

Setz TypeScript dort ein, wo Fehler teuer sind

In einem Dashboard sind Einheiten- und Typfehler die teuersten: eine Währung mit ihrer Untereinheit vertauscht, ein Prozentwert übergeben, wo ein Verhältnis erwartet wird, ein Datum im falschen Kalender. Branded Types fangen das zur Kompilierzeit ab und kosten wenige Zeilen.

type Toman = number & { readonly __brand: "Toman" };
type Ratio = number & { readonly __brand: "Ratio" }; // 0..1

const toman = (n: number) => n as Toman;
const ratio = (n: number) => n as Ratio;

function formatPercent(r: Ratio) {
  return `${(r * 100).toFixed(1)}%`;
}

formatPercent(ratio(0.42)); // ✅ "42.0%"
// formatPercent(toman(42)); // ❌ Kompilierfehler

Mit Tailwind Tokens bauen statt Klassen zu wiederholen

Tailwind verführt dazu, überall rounded-xl border border-white/10 bg-white/5 backdrop-blur zu schreiben. Beim zwanzigsten Mal bedeutet ein neues Kartendesign eine Suche durch das ganze Projekt. Die Lösung ist klein: Farben und Radien als CSS-Variablen definieren und im Tailwind-Theme registrieren. Danach ändert eine Variable das gesamte Dashboard.

:root {
  --surface: 0 0% 100% / 0.04;
  --hairline: 0 0% 100% / 0.10;
  --r-md: 0.875rem;
}

.card {
  background: hsl(var(--surface));
  border: 1px solid hsl(var(--hairline));
  border-radius: var(--r-md);
}

Was Nutzer immer wollen und wir immer vergessen

  • Eine Möglichkeit, Testdaten zu löschen. Alle starten mit ein paar Probebestellungen; lassen sie sich nicht entfernen, bleibt die Statistik für immer falsch.
  • Einen aktiven Zustand für jeden Menüpunkt. Ohne ihn verirren sich Nutzer in mehrstufigen Panels.
  • Bestätigungen für unumkehrbare Aktionen — aber nur für diese. Wer alles bestätigen lässt, erzieht Nutzer zum blinden Wegklicken.
  • Einen sichtbaren Zeitstempel der letzten Aktualisierung. Sonst weiß niemand, ob die Zahl aktuell ist oder aus dem Cache von gestern.

Der abschließende Test

Ein gutes Dashboard zeigt nicht die meisten Informationen. Es ist das, bei dem Nutzer nach zehn Sekunden wissen, was heute zu tun ist. Braucht diese Antwort drei Scrolls und zwei Menüs, liegt es nicht an den Daten — es liegt an der Reihenfolge.