Things 3: L'Art de la Simplicité Concentrée
Comment Things 3 a atteint la simplicité par les contraintes : hiérarchie naturelle, séparation When vs Deadline, design keyboard-first et retenue colorée significative. Avec patterns d'implémentation SwiftUI.
Things 3 : L'art de la simplicité ciblée
« Nous croyons que la simplicité est difficile. » — Cultured Code
Things 3 est souvent qualifié de plus beau gestionnaire de tâches jamais créé. Cette beauté naît d'une focalisation implacable sur l'essentiel et de l'élimination de tout le superflu.
Pourquoi Things 3 compte
Things 3 démontre que les contraintes créent la clarté. Là où les concurrents empilent les fonctionnalités (tâches récurrentes avec 47 options, projets avec dépendances et diagrammes de Gantt), Things pose la question : de quoi une personne a-t-elle réellement besoin pour accomplir ses tâches ?
Réalisations clés : - A atteint la simplicité visuelle sans sacrifier la profondeur - A rendu les dates significatives (Aujourd'hui, Ce soir, Un jour) - A créé une hiérarchie qui reflète la façon dont les humains pensent leur travail - A prouvé qu'une approche clavier-first peut être élégante - A établi la référence en matière de design natif macOS/iOS
Points clés à retenir
- Séparer « quand vais-je le faire ? » de « quelle est l'échéance ? » - Le modèle à double date de Things (Quand + Échéance) élimine l'ambiguïté qui affecte la plupart des applications de tâches ; planifier une intention et fixer une échéance ferme servent des objectifs différents
- Un jour est une fonctionnalité, pas un état d'échec - Offrir aux idées un espace sans pression évite l'anxiété de la boîte de réception ; les tâches dans Un jour n'encombrent pas Aujourd'hui et ne génèrent pas de culpabilité
- Les domaines ne se terminent jamais, les projets oui - Cette distinction reflète la cognition humaine : « Travail » est un domaine de vie permanent, « Livrer la v2.0 » a une ligne d'arrivée ; mélanger les deux crée de la confusion
- Réserver la couleur au sens - L'interface de Things est à 95 % neutre (noir, blanc, gris) ; la couleur n'apparaît que pour l'information sémantique (jaune=Aujourd'hui, rouge=Échéance, indigo=Ce soir)
- Compléter une tâche doit procurer une récompense - L'animation de la case à cocher (remplissage → coche → son) prend 500 ms mais libère de la dopamine ; accomplir des tâches devient intrinsèquement motivant
Principes de design fondamentaux
1. La hiérarchie naturelle
Things organise le travail comme les humains le conçoivent : du général (les domaines de vie) au spécifique (les tâches individuelles).
HIÉRARCHIE DE THINGS :
Area (Work, Personal, Health) ← Grandes catégories de vie
│
└── Project (Launch App) ← Objectif fini avec des tâches
│
└── Heading (Design) ← Regroupement optionnel
│
└── To-Do ← Élément actionnable unique
│
└── Checklist ← Sous-étapes au sein d'une tâche
Exemple :
┌─────────────────────────────────────────────────────────────┐
│ [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 │
└─────────────────────────────────────────────────────────────┘
L'insight clé : Les domaines ne se terminent jamais (ce sont des catégories de vie). Les projets se terminent (livrer avant vendredi). Cette distinction élimine la paralysie de planification.
Modèle de données :
// 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. Quand, pas seulement l'échéance
Things sépare « quand vais-je le faire ? » (Aujourd'hui, Ce soir, Un jour) de « quand cela doit-il être terminé ? » (Échéance). La plupart des applications confondent ces deux notions.
MODÈLE DE DATE TRADITIONNEL :
┌──────────────────────────────────────────────────────────────┐
│ Task: Write report │
│ Due: March 15th │
│ │
│ Problème : Le 15 mars, est-ce quand je vais le faire, │
│ ou quand c'est dû ? Si c'est l'échéance, │
│ quand devrais-je commencer ? │
└──────────────────────────────────────────────────────────────┘
MODÈLE DE DATE DE THINGS :
┌──────────────────────────────────────────────────────────────┐
│ Task: Write report │
│ When: Today (je vais y travailler aujourd'hui) │
│ Deadline: March 15th (doit être terminé avant cette date) │
│ │
│ Clarté : Deux questions distinctes, deux réponses distinctes│
└──────────────────────────────────────────────────────────────┘
LISTES INTELLIGENTES (filtrage automatique) :
┌───────────────────┬─────────────────────────────────────────┐
│ [I] INBOX │ Captures non catégorisées │
├───────────────────┼─────────────────────────────────────────┤
│ [*] 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 │
└───────────────────┴─────────────────────────────────────────┘
L'insight clé : « Un jour » n'est pas un état d'échec — c'est une soupape de décompression. Les idées ont un espace dédié sans encombrer Aujourd'hui.
Implémentation SwiftUI :
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. Clavier d'abord, compatible souris
L'ensemble des fonctionnalités est utilisable entièrement au clavier avec une efficacité qui repose sur la mémoire musculaire, tout en restant intuitif pour les utilisateurs de souris.
RACCOURCIS CLAVIER (patterns mémorisables) :
NAVIGATION :
Cmd+1-6 Accéder aux listes intelligentes (Inbox, Today, etc.)
Cmd+Up/Down Se déplacer entre les sections
Space Quick Look (aperçu des détails de la tâche)
MANIPULATION DES TÂCHES :
Cmd+K Terminer la tâche
Cmd+S Planifier (sélecteur When)
Cmd+Shift+D Définir une deadline
Cmd+Shift+M Déplacer vers un projet
Cmd+Shift+T Ajouter des tags
CRÉATION :
Space/Return Créer une nouvelle tâche (contextuel)
Cmd+N Nouvelle tâche dans Inbox
Cmd+Opt+N Nouveau projet
Quick Entry (raccourci global) :
Ctrl+Space Ouvre la saisie rapide flottante depuis n'importe où
┌─────────────────────────────────────────────────┐
│ [ ] | │
│ La nouvelle tâche apparaît où que vous soyez │
└─────────────────────────────────────────────────┘
L'idée clé : Les raccourcis doivent être découvrables mais jamais obligatoires. L'interface suggère les touches sans les imposer.
Pattern CSS (indicateurs de raccourcis) :
.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;
}
/* Afficher au survol */
.action-button:hover .shortcut-hint {
opacity: 1;
}
/* Toujours visible lors de la navigation au clavier */
.action-button:focus-visible .shortcut-hint {
opacity: 1;
}
Quick Entry en SwiftUI :
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 (visual only)
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. Retenue visuelle
Things utilise la couleur avec parcimonie, la réservant uniquement pour véhiculer du sens. La majeure partie de l'interface est neutre, permettant aux éléments colorés de ressortir.
UTILISATION DE LA COULEUR DANS THINGS :
Neutre (95% de l'interface) :
├── Arrière-plan : Blanc pur / Gris foncé
├── Texte : Noir / Blanc
├── Bordures : Gris très subtil
└── Icônes : Monochromes
Couleur porteuse de sens (5%) :
├── Jaune [*] = Aujourd'hui
├── Rouge [!] = Échéance / En retard
├── Bleu [#] = Tags (assignés par l'utilisateur)
├── Vert [x] = Animation de complétion
└── Indigo [E] = Ce soir
EXEMPLE VISUEL :
┌─────────────────────────────────────────────────────────────┐
│ AUJOURD'HUI [*] (jaune) │
├─────────────────────────────────────────────────────────────┤
│ ( ) Rédiger la documentation │
│ Nom du projet - [!] Échéance demain (rouge) │
│ │
│ ( ) Relire les pull requests │
│ [#] travail (tag bleu) │
│ │
│ ( ) Appeler le dentiste │
│ [E] Ce soir (badge indigo) │
└─────────────────────────────────────────────────────────────┘
Remarque : La majeure partie du texte est en noir. La couleur n'apparaît que là où elle porte du sens.
L'enseignement clé : Quand tout est coloré, rien ne ressort. Réservez la couleur pour l'information.
Pattern CSS :
:root {
/* Palette neutre (majeure partie de l'UI) */
--surface-primary: #ffffff;
--surface-secondary: #f5f5f7;
--text-primary: #1d1d1f;
--text-secondary: #86868b;
--border: rgba(0, 0, 0, 0.08);
/* Couleurs sémantiques (utilisées avec parcimonie) */
--color-today: #f5c518;
--color-deadline: #ff3b30;
--color-evening: #5856d6;
--color-complete: #34c759;
--color-tag: #007aff;
}
/* Ligne de tâche - majoritairement neutre */
.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);
}
/* La couleur uniquement pour le sens */
.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. Une complétion gratifiante
L'animation de complétion procure une récompense, pas un simple retour visuel. Un son « plink » satisfaisant et une animation de coche fluide rendent l'achèvement des tâches agréable.
SÉQUENCE DE COMPLÉTION :
Image 1 : ○ Titre de la tâche
Images 2-4 : ◔ (le cercle se remplit dans le sens horaire)
Images 5-7 : ● (entièrement rempli, courte pause)
Images 8-10 : ✓ (la coche apparaît, légèrement agrandie)
Images 11+ : La ligne glisse, la liste comble l'espace
Audio : « Plink » discret aux images 7-8 (moment de complétion)
Haptique : Légère vibration sur iOS à la complétion
Implémentation SwiftUI :
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)
// 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
}
}
}
Patterns transférables
Pattern 1 : Détails progressifs des tâches
Les détails de la tâche se développent directement dans la liste plutôt que de naviguer vers une vue séparée.
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)
}
}
Pattern 2 : Saisie en langage naturel
Things analyse le langage naturel pour définir les dates : « 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 : Glisser-déposer dans les listes
La réorganisation repose sur la manipulation directe avec un retour visuel explicite.
.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;
}
Leçons de design
- Séparer les préoccupations : « Quand vais-je m'en occuper ? » et « Quelle est l'échéance ? » sont deux questions distinctes
- Retenue dans l'usage de la couleur : Réserver la couleur au sens ; le neutre est la norme
- L'accomplissement est une récompense : Rendre la finalisation des tâches gratifiante
- Le clavier accélère, la souris accueille : Concevoir pour les deux sans compromettre l'un ou l'autre
- Hiérarchies naturelles : Refléter la façon dont les humains pensent déjà le travail (domaines → projets → tâches)
- Un jour est une fonctionnalité : Offrir aux idées un espace qui ne crée pas de pression
Questions fréquentes
Quelle est la différence entre « When » et « Deadline » dans Things 3 ?
« When » répond à « quand vais-je travailler dessus ? » (Today, This Evening, Someday, ou une date précise). « Deadline » répond à « quand cela doit-il être terminé ? » La plupart des applications de gestion de tâches confondent ces deux notions, alors qu'elles servent des objectifs différents. Une tâche peut être due vendredi (Deadline) mais vous prévoyez d'y travailler mercredi (When). Cette séparation vous permet d'organiser votre journée autour de vos intentions tout en suivant les échéances fermes.
Comment fonctionne la hiérarchie de Things 3 (Areas, Projects, Tasks) ?
Les Areas sont de grandes catégories de vie qui ne se terminent jamais (Travail, Personnel, Santé). Les Projects sont des objectifs finis avec un état d'achèvement clair (Lancer une application, Planifier des vacances). Les Tasks sont des éléments d'action individuels. Les Headings regroupent optionnellement les tâches au sein des projets. Cela reflète la façon dont les humains pensent naturellement le travail : des responsabilités continues contiennent des objectifs limités dans le temps, qui contiennent des actions discrètes.
Pourquoi Things 3 utilise-t-il si peu de couleur ?
Things réserve la couleur exclusivement au sens sémantique. L'interface est à 95 % neutre (noir, blanc, gris) de sorte que lorsqu'une couleur apparaît, elle communique quelque chose : le jaune signifie Today, le rouge signifie Deadline/En retard, l'indigo signifie Evening, le bleu marque les tags. Si tout était coloré, rien ne ressortirait. Cette retenue rend les couleurs significatives impossibles à manquer.
Qu'est-ce que « Someday » dans Things 3 et pourquoi est-ce précieux ?
Someday est un espace dédié aux tâches que vous souhaitez garder en mémoire sans qu'elles encombrent vos listes actives. Ce n'est pas un état d'échec — c'est une soupape de décompression. Les idées pour « un jour » (apprendre l'espagnol, lire ce livre) ont un espace sans générer de culpabilité quotidienne. Vous pouvez passer en revue Someday chaque semaine et promouvoir des tâches vers Today quand vous êtes prêt.
Comment fonctionne l'animation d'achèvement de Things 3 ?
Lorsque vous appuyez sur la case à cocher, le cercle se remplit dans le sens horaire (200 ms), puis une coche apparaît avec un léger rebond (spring animation), accompagnée d'un doux son « plink » et d'un retour haptique sur iOS. La ligne glisse ensuite vers l'extérieur (150 ms). La séquence complète dure environ 500 ms, mais ce timing délibéré crée une petite libération de dopamine qui rend l'achèvement des tâches gratifiant.
Ressources
- Site web : culturedcode.com/things
- Philosophie de design : Articles du blog de Cultured Code sur la simplicité
- Raccourcis clavier : Menu Aide intégré (Cmd+/)
- Intégration GTD : Comment Things s'articule avec la méthodologie Getting Things Done