Things 3: Die Kunst der fokussierten Einfachheit

Wie Things 3 Einfachheit durch Einschränkungen erreichte: natürliche Hierarchie, When vs Deadline Trennung, Tastatur-first Design und bedeutungsvolle Farbzurückhaltung. Mit SwiftUI-Implementierungsmustern.

5 Min. Lesezeit 953 Worter
Things 3: Die Kunst der fokussierten Einfachheit screenshot

Things 3: Die Kunst der fokussierten Einfachheit

„Wir glauben, dass einfach schwer ist." — Cultured Code

Things 3 wird oft als der schönste Task-Manager bezeichnet, der je entwickelt wurde. Diese Schönheit entsteht durch kompromisslosen Fokus auf das Wesentliche und die Eliminierung von allem anderen.


Warum Things 3 wichtig ist

Things 3 zeigt, dass Einschränkungen Klarheit schaffen. Während Konkurrenten Features anhäufen (wiederkehrende Aufgaben mit 47 Optionen, Projekte mit Abhängigkeiten und Gantt-Diagrammen), fragt Things: Was braucht ein Mensch wirklich, um Dinge erledigt zu bekommen?

Zentrale Errungenschaften: - Visuelle Einfachheit ohne Einbußen bei der Tiefe erreicht - Datumsangaben bedeutungsvoll gemacht (Today, Evening, Someday) - Eine Hierarchie geschaffen, die widerspiegelt, wie Menschen über Arbeit denken - Bewiesen, dass Tastatur-orientiert auch schön sein kann - Den Standard für natives macOS/iOS-App-Design gesetzt


Zentrale Erkenntnisse

  1. Trenne „Wann werde ich das tun?" von „Wann ist es fällig?" - Things' Dual-Date-Modell (When + Deadline) beseitigt die Mehrdeutigkeit, unter der die meisten Task-Apps leiden; geplante Absicht und harte Deadlines dienen unterschiedlichen Zwecken
  2. Someday ist ein Feature, kein Versagenszustand - Ideen einen druckfreien Ort zu geben, verhindert Inbox-Angst; Aufgaben in Someday überladen nicht Today und lösen keine Schuldgefühle aus
  3. Areas werden nie abgeschlossen, Projects schon - Diese Unterscheidung spiegelt menschliche Kognition wider: „Arbeit" ist ein fortlaufender Lebensbereich, „Ship v2.0" hat eine Ziellinie; diese zu vermischen erzeugt Verwirrung
  4. Reserviere Farbe für Bedeutung - Things' Oberfläche ist zu 95% neutral (Schwarz, Weiß, Grau); Farbe erscheint nur für semantische Information (Gelb=Today, Rot=Deadline, Indigo=Evening)
  5. Erledigung sollte sich belohnend anfühlen - Die Checkbox-Animation (Füllen → Häkchen → Sound) dauert 500ms, liefert aber Dopamin; Aufgaben zu erledigen wird intrinsisch motivierend

Grundlegende Design-Prinzipien

1. Die natürliche Hierarchie

Things organisiert Arbeit so, wie Menschen darüber denken: vom Breiten (Lebensbereiche) zum Spezifischen (einzelne Aufgaben).

THINGS HIERARCHY:

Area (Work, Personal, Health)     ← Broad life categories
  │
  └── Project (Launch App)        ← Finite goal with tasks
        │
        └── Heading (Design)      ← Optional grouping
              │
              └── To-Do           ← Single actionable item
                    │
                    └── Checklist ← Sub-steps within task

Example:
┌─────────────────────────────────────────────────────────────┐
│ [A] WORK (Area)                                             │
│   ├── [P] Ship Version 2.0 (Project)                        │
│   │     ├── [H] Design                                      │
│   │     │     ├── [ ] Create new icons                      │
│   │     │     └── [ ] Update color palette                  │
│   │     └── [H] Development                                 │
│   │           ├── [ ] Implement auth flow                   │
│   │           └── [ ] Add analytics                         │
│   └── [P] Website Redesign (Project)                        │
│         └── [ ] Write copy for landing page                 │
└─────────────────────────────────────────────────────────────┘

Die zentrale Erkenntnis: Areas werden nie abgeschlossen (sie sind Lebenskategorien). Projects werden abgeschlossen (bis Freitag ausliefern). Diese Unterscheidung eliminiert Planungsparalyse.

Datenmodell:

// Things' hierarchy expressed in code
struct Area: Identifiable {
    let id: UUID
    var title: String
    var projects: [Project]
    var tasks: [Task]  // Loose tasks not in projects
}

struct Project: Identifiable {
    let id: UUID
    var title: String
    var notes: String?
    var deadline: Date?
    var headings: [Heading]
    var tasks: [Task]
    var isComplete: Bool
}

struct Heading: Identifiable {
    let id: UUID
    var title: String
    var tasks: [Task]
}

struct Task: Identifiable {
    let id: UUID
    var title: String
    var notes: String?
    var when: TaskSchedule?
    var deadline: Date?
    var tags: [Tag]
    var checklist: [ChecklistItem]
    var isComplete: Bool
}

enum TaskSchedule {
    case today
    case evening
    case specificDate(Date)
    case someday
}

2. When, nicht nur Due

Things trennt „Wann werde ich das tun?" (Today, Evening, Someday) von „Wann muss das erledigt sein?" (Deadline). Die meisten Apps vermischen diese Konzepte.

TRADITIONAL DATE MODEL:
┌──────────────────────────────────────────────────────────────┐
│ Task: Write report                                           │
│ Due: March 15th                                              │
│                                                              │
│ Problem: Is March 15th when I'll do it, or when it's due?   │
│          If it's due, when should I start?                  │
└──────────────────────────────────────────────────────────────┘

THINGS DATE MODEL:
┌──────────────────────────────────────────────────────────────┐
│ Task: Write report                                           │
│ When: Today (I'll work on it today)                         │
│ Deadline: March 15th (must be complete by then)             │
│                                                              │
│ Clarity: Two separate questions, two separate answers       │
└──────────────────────────────────────────────────────────────┘

SMART LISTS (automatic filtering):

┌───────────────────┬─────────────────────────────────────────┐
│ [I] INBOX         │ Uncategorized captures                  │
├───────────────────┼─────────────────────────────────────────┤
│ [*] TODAY         │ when == .today OR deadline == today     │
├───────────────────┼─────────────────────────────────────────┤
│ [E] THIS EVENING  │ when == .evening                        │
├───────────────────┼─────────────────────────────────────────┤
│ [D] UPCOMING      │ when == .specificDate (future)          │
├───────────────────┼─────────────────────────────────────────┤
│ [A] ANYTIME       │ when == nil AND deadline == nil         │
├───────────────────┼─────────────────────────────────────────┤
│ [S] SOMEDAY       │ when == .someday                        │
├───────────────────┼─────────────────────────────────────────┤
│ [L] LOGBOOK       │ isComplete == true                      │
└───────────────────┴─────────────────────────────────────────┘

Die zentrale Erkenntnis: „Someday" ist kein Versagenszustand – es ist ein Druckventil. Ideen haben einen Platz, ohne Today zu überladen.

SwiftUI-Implementierung:

struct WhenPicker: View {
    @Binding var schedule: TaskSchedule?
    @Binding var deadline: Date?
    @State private var showDatePicker = false

    var body: some View {
        VStack(spacing: 12) {
            // Quick options
            HStack(spacing: 8) {
                WhenButton(
                    icon: "star.fill",
                    label: "Today",
                    color: .yellow,
                    isSelected: schedule == .today
                ) {
                    schedule = .today
                }

                WhenButton(
                    icon: "moon.fill",
                    label: "Evening",
                    color: .indigo,
                    isSelected: schedule == .evening
                ) {
                    schedule = .evening
                }

                WhenButton(
                    icon: "calendar",
                    label: "Pick Date",
                    color: .red,
                    isSelected: showDatePicker
                ) {
                    showDatePicker = true
                }

                WhenButton(
                    icon: "archivebox.fill",
                    label: "Someday",
                    color: .brown,
                    isSelected: schedule == .someday
                ) {
                    schedule = .someday
                }
            }

            // Deadline (separate from "when")
            if let deadline = deadline {
                HStack {
                    Image(systemName: "flag.fill")
                        .foregroundStyle(.red)
                    Text("Deadline: \(deadline, style: .date)")
                    Spacer()
                    Button("Clear") { self.deadline = nil }
                }
                .font(.caption)
            }
        }
    }
}

struct WhenButton: View {
    let icon: String
    let label: String
    let color: Color
    let isSelected: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            VStack(spacing: 4) {
                Image(systemName: icon)
                    .font(.title2)
                Text(label)
                    .font(.caption2)
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 8)
            .background(isSelected ? color.opacity(0.2) : .clear)
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
        .foregroundStyle(isSelected ? color : .secondary)
    }
}

3. Tastatur-zuerst, Maus-freundlich

Alles lässt sich vollständig über die Tastatur mit Muskelgedächtnis-Effizienz bedienen, bleibt aber gleichzeitig intuitiv für Mausbenutzer.

TASTATURKÜRZEL (einprägsame Muster):

NAVIGATION:
  Cmd+1-6      Zu Smart-Listen springen (Inbox, Heute, etc.)
  Cmd+Up/Down  Zwischen Abschnitten wechseln
  Space        Schnellansicht (Aufgabendetails anzeigen)

AUFGABENBEARBEITUNG:
  Cmd+K        Aufgabe abschließen
  Cmd+S        Planen (Wann-Auswahl)
  Cmd+Shift+D  Deadline setzen
  Cmd+Shift+M  In Projekt verschieben
  Cmd+Shift+T  Tags hinzufügen

ERSTELLEN:
  Space/Return Neue Aufgabe erstellen (kontextabhängig)
  Cmd+N        Neue Aufgabe in Inbox
  Cmd+Opt+N    Neues Projekt

Schnelleingabe (globaler Hotkey):
  Ctrl+Space   Öffnet schwebendes Schnelleingabefeld von überall
  ┌─────────────────────────────────────────────────┐
  │ [ ] |                                           │
  │   Neue Aufgabe erscheint, wo immer du bist      │
  └─────────────────────────────────────────────────┘

Die zentrale Erkenntnis: Tastaturkürzel sollten entdeckbar, aber nicht erforderlich sein. Die Oberfläche deutet Tasten an, ohne sie zu verlangen.

CSS-Pattern (Shortcut-Hinweise):

.action-button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
}

.shortcut-hint {
  font-size: 11px;
  font-family: var(--font-mono);
  padding: 2px 6px;
  background: var(--surface-subtle);
  border-radius: 4px;
  color: var(--text-tertiary);
  opacity: 0;
  transition: opacity 0.15s ease;
}

/* Bei Hover anzeigen */
.action-button:hover .shortcut-hint {
  opacity: 1;
}

/* Immer sichtbar bei Tastatur-Fokus */
.action-button:focus-visible .shortcut-hint {
  opacity: 1;
}

SwiftUI Schnelleingabe:

struct QuickEntryPanel: View {
    @Binding var isPresented: Bool
    @State private var taskTitle = ""
    @FocusState private var isFocused: Bool

    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 12) {
                // Checkbox (nur visuell)
                Circle()
                    .strokeBorder(.tertiary, lineWidth: 1.5)
                    .frame(width: 20, height: 20)

                // Task input
                TextField("New To-Do", text: $taskTitle)
                    .textFieldStyle(.plain)
                    .font(.body)
                    .focused($isFocused)
                    .onSubmit {
                        createTask()
                    }
            }
            .padding()
            .background(.ultraThinMaterial)
            .cornerRadius(12)
            .shadow(color: .black.opacity(0.2), radius: 20, y: 10)
        }
        .frame(width: 500)
        .onAppear {
            isFocused = true
        }
        .onExitCommand {
            isPresented = false
        }
    }

    private func createTask() {
        guard !taskTitle.isEmpty else { return }
        // Create task in Inbox
        TaskStore.shared.createTask(title: taskTitle)
        taskTitle = ""
    }
}

4. Visuelle Zurückhaltung

Things setzt Farbe sparsam ein und reserviert sie ausschließlich für bedeutungsvolle Elemente. Der Großteil der Oberfläche ist neutral gehalten, sodass farbige Elemente hervorstechen.

COLOR USAGE IN THINGS:

Neutral (95% of interface):
├── Background: Pure white / Deep gray
├── Text: Black / White
├── Borders: Very subtle gray
└── Icons: Monochrome

Meaningful Color (5%):
├── Yellow [*] = Today
├── Red [!] = Deadline / Overdue
├── Blue [#] = Tags (user-assigned)
├── Green [x] = Completion animation
└── Indigo [E] = Evening

VISUAL EXAMPLE:

┌─────────────────────────────────────────────────────────────┐
│ TODAY                                        [*] (yellow)   │
├─────────────────────────────────────────────────────────────┤
│ ( ) Write documentation                                     │
│     Project Name - [!] Due tomorrow (red)                   │
│                                                             │
│ ( ) Review pull requests                                    │
│     [#] work (blue tag)                                     │
│                                                             │
│ ( ) Call dentist                                            │
│     [E] This Evening (indigo badge)                         │
└─────────────────────────────────────────────────────────────┘

Notice: Most text is black. Color only appears where it carries meaning.

Die zentrale Erkenntnis: Wenn alles bunt ist, sticht nichts hervor. Reserviere Farbe für Information.

CSS-Pattern:

:root {
  /* Neutral palette (most of UI) */
  --surface-primary: #ffffff;
  --surface-secondary: #f5f5f7;
  --text-primary: #1d1d1f;
  --text-secondary: #86868b;
  --border: rgba(0, 0, 0, 0.08);

  /* Semantic colors (used sparingly) */
  --color-today: #f5c518;
  --color-deadline: #ff3b30;
  --color-evening: #5856d6;
  --color-complete: #34c759;
  --color-tag: #007aff;
}

/* Task row - mostly neutral */
.task-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-primary);
  color: var(--text-primary);
}

/* Color only for meaning */
.deadline-badge {
  font-size: 12px;
  color: var(--color-deadline);
}

.deadline-badge.overdue {
  font-weight: 600;
  color: var(--color-deadline);
}

.tag {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--color-tag);
  color: white;
}

.evening-badge {
  color: var(--color-evening);
}

5. Befriedigende Erledigung

Die Erledigungsanimation liefert Belohnung, nicht bloßes Feedback. Ein zufriedenstellendes „Plink"-Geräusch und eine flüssige Häkchen-Animation machen das Abschließen von Aufgaben zu einem guten Gefühl.

COMPLETION SEQUENCE:

Frame 1:     ○ Task title
Frame 2-4:   ◔ (circle fills clockwise)
Frame 5-7:   ● (fully filled, brief pause)
Frame 8-10:  ✓ (check appears, scales up slightly)
Frame 11+:   Row slides away, list closes gap

Audio: Soft "plink" at Frame 7-8 (completion moment)
Haptic: Light tap on iOS at completion

SwiftUI-Implementierung:

struct CompletableCheckbox: View {
    @Binding var isComplete: Bool
    @State private var animationPhase: AnimationPhase = .unchecked

    enum AnimationPhase {
        case unchecked, filling, checked, done
    }

    var body: some View {
        Button {
            completeWithAnimation()
        } label: {
            ZStack {
                // Background circle
                Circle()
                    .strokeBorder(.tertiary, lineWidth: 1.5)
                    .frame(width: 22, height: 22)

```swift
                // Fill animation
                Circle()
                    .trim(from: 0, to: fillAmount)
                    .stroke(.green, lineWidth: 1.5)
                    .frame(width: 22, height: 22)
                    .rotationEffect(.degrees(-90))

                // Checkmark
                Image(systemName: "checkmark")
                    .font(.system(size: 12, weight: .bold))
                    .foregroundStyle(.green)
                    .scaleEffect(checkScale)
                    .opacity(checkOpacity)
            }
        }
        .buttonStyle(.plain)
        .sensoryFeedback(.success, trigger: animationPhase == .checked)
    }

    private var fillAmount: CGFloat {
        switch animationPhase {
        case .unchecked: return 0
        case .filling: return 1
        case .checked, .done: return 1
        }
    }

    private var checkScale: CGFloat {
        animationPhase == .checked ? 1.2 : (animationPhase == .done ? 1.0 : 0)
    }

    private var checkOpacity: CGFloat {
        animationPhase == .unchecked || animationPhase == .filling ? 0 : 1
    }

    private func completeWithAnimation() {
        // Phase 1: Fill the circle
        withAnimation(.easeInOut(duration: 0.2)) {
            animationPhase = .filling
        }

        // Phase 2: Show checkmark with bounce
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            withAnimation(.spring(response: 0.3, dampingFraction: 0.5)) {
                animationPhase = .checked
            }
        }

        // Phase 3: Settle and mark complete
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            withAnimation(.easeOut(duration: 0.15)) {
                animationPhase = .done
            }
            isComplete = true
        }
    }
}

Übertragbare Muster

Muster 1: Progressive Aufgabendetails

Aufgabendetails werden inline erweitert, anstatt zu einer separaten Ansicht zu navigieren.

struct ExpandableTask: View {
    let task: Task
    @State private var isExpanded = false

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            // Always visible row
            TaskRow(task: task, isExpanded: $isExpanded)

            // Expandable details
            if isExpanded {
                TaskDetails(task: task)
                    .padding(.leading, 44) // Align with title
                    .transition(.asymmetric(
                        insertion: .push(from: .top).combined(with: .opacity),
                        removal: .push(from: .bottom).combined(with: .opacity)
                    ))
            }
        }
        .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isExpanded)
    }
}

Muster 2: Natürlichsprachliche Eingabe

Things analysiert natürliche Sprache zur Datumsangabe: „tomorrow", „next week", „June 15".

function parseNaturalDate(input: string): TaskSchedule | null {
  const lower = input.toLowerCase();

  // Today shortcuts
  if (['today', 'tod', 'now'].includes(lower)) {
    return { type: 'today' };
  }

  // Evening
  if (['tonight', 'evening', 'eve'].includes(lower)) {
    return { type: 'evening' };
  }

  // Tomorrow
  if (['tomorrow', 'tom', 'tmrw'].includes(lower)) {
    const date = new Date();
    date.setDate(date.getDate() + 1);
    return { type: 'date', date };
  }

  // Someday
  if (['someday', 'later', 'eventually'].includes(lower)) {
    return { type: 'someday' };
  }

  // Relative days
  const inMatch = lower.match(/in (\d+) days?/);
  if (inMatch) {
    const date = new Date();
    date.setDate(date.getDate() + parseInt(inMatch[1]));
    return { type: 'date', date };
  }

  // Day names
  const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
  const dayIndex = days.findIndex(d => lower.startsWith(d.slice(0, 3)));
  if (dayIndex !== -1) {
    const date = getNextDayOfWeek(dayIndex);
    return { type: 'date', date };
  }

  return null;
}

Pattern 3: Listenbasiertes Drag-and-Drop

Das Neuordnen erfolgt durch direkte Manipulation mit klarem visuellen Feedback.

.task-row {
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.task-row.dragging {
  transform: scale(1.02);
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.15),
    0 0 0 2px var(--color-focus);
  z-index: 100;
}

.task-row.drop-above::before {
  content: '';
  position: absolute;
  top: -2px;
  left: 44px;
  right: 16px;
  height: 4px;
  background: var(--color-focus);
  border-radius: 2px;
}

.task-row.drop-below::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 44px;
  right: 16px;
  height: 4px;
  background: var(--color-focus);
  border-radius: 2px;
}

Design-Lektionen

  1. Belange trennen: „Wann werde ich das tun?" vs. „Wann ist es fällig?" sind unterschiedliche Fragen
  2. Zurückhaltung bei Farbe: Farbe für Bedeutung reservieren; Neutral ist der Standard
  3. Abschluss ist Belohnung: Das Erledigen von Aufgaben soll sich gut anfühlen
  4. Tastatur beschleunigt, Maus heißt willkommen: Für beide gestalten, ohne Kompromisse bei einem zu machen
  5. Natürliche Hierarchien: Widerspiegeln, wie Menschen bereits über Arbeit denken (Bereiche → Projekte → Aufgaben)
  6. Irgendwann ist ein Feature: Ideen ein Zuhause geben, das keinen Druck erzeugt

Häufig gestellte Fragen

Was ist der Unterschied zwischen „Wann" und „Frist" in Things 3?

„Wann" beantwortet „wann werde ich daran arbeiten?" (Heute, Heute Abend, Irgendwann oder ein bestimmtes Datum). „Frist" beantwortet „wann muss dies fertig sein?" Die meisten Aufgaben-Apps vermischen diese, aber sie dienen unterschiedlichen Zwecken. Eine Aufgabe könnte am Freitag fällig sein (Frist), aber du planst, am Mittwoch daran zu arbeiten (Wann). Diese Trennung ermöglicht es dir, deinen Tag nach Absichten zu planen und gleichzeitig harte Fälligkeitstermine zu verfolgen.

Wie funktioniert die Hierarchie von Things 3 (Bereiche, Projekte, Aufgaben)?

Bereiche sind breite Lebenskategorien, die nie abgeschlossen werden (Arbeit, Persönlich, Gesundheit). Projekte sind endliche Ziele mit einem klaren Endzustand (App veröffentlichen, Urlaub planen). Aufgaben sind einzelne umsetzbare Punkte. Überschriften gruppieren optional Aufgaben innerhalb von Projekten. Dies spiegelt wider, wie Menschen natürlich über Arbeit denken: Laufende Verantwortlichkeiten enthalten zeitgebundene Ziele, die diskrete Aktionen enthalten.

Warum verwendet Things 3 so wenig Farbe?

Things reserviert Farbe ausschließlich für semantische Bedeutung. Die Benutzeroberfläche ist zu 95% neutral (Schwarz, Weiß, Grau), sodass Farbe, wenn sie erscheint, kommuniziert: Gelb bedeutet Heute, Rot bedeutet Frist/Überfällig, Indigo bedeutet Abend, Blau markiert Tags. Wenn alles bunt wäre, würde nichts hervorstechen. Die Zurückhaltung macht die bedeutungsvollen Farben unmöglich zu übersehen.

Was ist „Irgendwann" in Things 3 und warum ist es wertvoll?

Irgendwann ist ein dedizierter Bereich für Aufgaben, die du dir merken möchtest, die aber nicht deine aktiven Listen überladen sollen. Es ist kein Versagenszustand – es ist ein Druckentlastungsventil. Ideen für „irgendwann" (Spanisch lernen, dieses Buch lesen) haben ein Zuhause, ohne tägliche Schuldgefühle zu erzeugen. Du kannst Irgendwann wöchentlich durchgehen und Aufgaben zu Heute befördern, wenn du bereit bist.

Wie funktioniert die Abschluss-Animation von Things 3?

Wenn du auf das Kontrollkästchen tippst, füllt sich der Kreis im Uhrzeigersinn (200ms), dann erscheint ein Häkchen mit einem leichten Bounce (Spring-Animation), begleitet von einem sanften „Plink"-Geräusch und haptischem Feedback auf iOS. Die Zeile gleitet dann weg (150ms). Die gesamte Sequenz dauert etwa 500ms, aber dieses bewusste Timing erzeugt einen kleinen Dopamin-Kick, der das Abschließen von Aufgaben belohnend erscheinen lässt.


Ressourcen

  • Website: culturedcode.com/things
  • Design-Philosophie: Cultured Code Blogbeiträge über Einfachheit
  • Tastaturkürzel: Integriertes Hilfe-Menü (Cmd+/)
  • GTD-Integration: Wie Things die Getting Things Done-Methodik abbildet