Design Study: Things 3's Focused Simplicity
Things 3 won multiple Apple Design Awards and earned a reputation as the most beautiful task manager ever made.[^1]
Why Does Things 3 Matter to Product Designers?
Things 3 demonstrates that constraints create clarity. While competitors pile on features (recurring tasks with 47 options, projects with dependencies and Gantt charts), Things asks: what does a person actually need to get things done?
The design achievements: - Achieved visual simplicity without sacrificing depth - Made dates meaningful with the When/Deadline separation - Created a hierarchy that mirrors how humans think about work - Proved keyboard-first can be beautiful - Set the standard for macOS/iOS native app design
Cultured Code, the makers of Things, summarized their philosophy: “We believe simple is hard.”
What Is the Natural Hierarchy and Why Does It Work?
Things organizes work the way humans naturally think about it: from the broad (areas of life) to the specific (individual tasks).
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
The key insight: Areas never complete (they’re life categories like “Work” or “Health”). Projects complete (ship by Friday). This distinction eliminates the paralysis of not knowing what “done” means.
Swift Data Model
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 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
}
How Does the When/Deadline Split Solve Date Confusion?
Most task managers conflate two different questions: “When will I work on this?” and “When must this be done?” Things separates them.
TRADITIONAL DATE MODEL:
┌──────────────────────────────────────────────────────────────┐
│ Task: Write report │
│ Due: March 15th │
│ │
│ Problem: Is March 15th when I'll do it, or when it's due? │
└──────────────────────────────────────────────────────────────┘
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 │
└──────────────────────────────────────────────────────────────┘
The smart lists flow from this model:
| List | Filter Logic |
|---|---|
| Inbox | Uncategorized captures |
| Today | when == .today OR deadline == today |
| This Evening | when == .evening |
| Upcoming | when == .specificDate (future) |
| Anytime | when == nil AND deadline == nil |
| Someday | when == .someday |
| Logbook | isComplete == true |
The key insight: “Someday” isn’t a failure state—it’s a pressure release valve. Ideas have a place without cluttering Today.
SwiftUI When Picker
struct WhenPicker: View {
@Binding var schedule: TaskSchedule?
@Binding var deadline: Date?
@State private var showDatePicker = false
var body: some View {
VStack(spacing: 12) {
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)
}
}
}
}
Why Does Things Use So Little Color?
Things uses color sparingly—only for meaning. 95% of the interface is neutral, letting colored elements actually communicate information.
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 ✓ = Completion animation
└── Indigo 🌙 = Evening
The key insight: When everything is colorful, nothing stands out. Reserve color for 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;
}
.evening-badge {
color: var(--color-evening);
}
.tag {
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
background: var(--color-tag);
color: white;
}
How Does Things Make Completion Feel Rewarding?
The completion animation isn’t just feedback—it’s a reward. A satisfying sound and smooth animation make completing tasks feel good.
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 completion moment
Haptic: Light tap on iOS
SwiftUI Checkbox Animation
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 {
Circle()
.strokeBorder(.tertiary, lineWidth: 1.5)
.frame(width: 22, height: 22)
Circle()
.trim(from: 0, to: fillAmount)
.stroke(.green, lineWidth: 1.5)
.frame(width: 22, height: 22)
.rotationEffect(.degrees(-90))
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, .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() {
withAnimation(.easeInOut(duration: 0.2)) {
animationPhase = .filling
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation(.spring(response: 0.3, dampingFraction: 0.5)) {
animationPhase = .checked
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.easeOut(duration: 0.15)) {
animationPhase = .done
}
isComplete = true
}
}
}
What Patterns Can You Steal From Things?
Pattern 1: Natural Language Date Parsing
Things parses natural language to set dates: “tomorrow”, “next week”, “June 15”.
function parseNaturalDate(input: string): TaskSchedule | null {
const lower = input.toLowerCase();
if (['today', 'tod', 'now'].includes(lower)) {
return { type: 'today' };
}
if (['tonight', 'evening', 'eve'].includes(lower)) {
return { type: 'evening' };
}
if (['tomorrow', 'tom', 'tmrw'].includes(lower)) {
const date = new Date();
date.setDate(date.getDate() + 1);
return { type: 'date', date };
}
if (['someday', 'later', 'eventually'].includes(lower)) {
return { type: 'someday' };
}
const inMatch = lower.match(/in (\d+) days?/);
if (inMatch) {
const date = new Date();
date.setDate(date.getDate() + parseInt(inMatch[1]));
return { type: 'date', date };
}
return null;
}
Pattern 2: Keyboard Shortcut Hints
Shortcuts should be discoverable but not required.
.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;
}
.action-button:hover .shortcut-hint {
opacity: 1;
}
.action-button:focus-visible .shortcut-hint {
opacity: 1;
}
Pattern 3: Progressive Task Details
Task details expand inline rather than navigating to a separate view.
struct ExpandableTask: View {
let task: Task
@State private var isExpanded = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
TaskRow(task: task, isExpanded: $isExpanded)
if isExpanded {
TaskDetails(task: task)
.padding(.leading, 44)
.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)
}
}
Frequently Asked Questions
What makes Things 3 different from other task managers?
Things 3 separates "when will I work on this?" from "when is this due?"—two questions most apps conflate into a single date field. It also uses a natural hierarchy (Areas → Projects → Tasks) that mirrors how humans think about work, with Areas as ongoing life categories that never complete and Projects as finite goals that do.
Why does Things 3 use so little color in its interface?
Things reserves color for meaning. When 95% of the interface is neutral, the yellow "Today" star, red deadline flag, and indigo "Evening" badge actually communicate information. Colorful interfaces train users to ignore color; restrained interfaces let color serve as a signal.
How does Things 3's "Someday" list reduce task management stress?
Someday acts as a pressure release valve. Ideas and tasks that you want to remember but aren't committed to completing have a home that doesn't clutter your Today or Upcoming views. This acknowledges that not every captured idea needs to create urgency—some things genuinely belong in "eventually" status.
Can I implement Things-style completion animations in my app?
Yes. The pattern involves three phases: (1) a circle that fills clockwise over ~200ms, (2) a checkmark that appears with a slight bounce/scale at completion, and (3) a row slide-away animation. Add haptic feedback on iOS and an optional audio cue for the full satisfying effect.
What design principles from Things 3 apply to other apps?
Five principles transfer broadly: (1) separate concerns—don't conflate different questions into one UI element; (2) restraint with color—reserve it for meaning; (3) completion as reward—make finishing things feel good; (4) keyboard-first, mouse-friendly—design for power users without excluding casual ones; (5) natural hierarchies—mirror how users already think about the domain.
Quick Summary
Things 3’s success comes from ruthless focus on what matters and elimination of everything else. The When/Deadline split solves real confusion that other apps ignore. The natural hierarchy matches how humans think about work. Color restraint lets the few colored elements communicate meaning. The satisfying completion animation makes finishing tasks feel rewarding. For designers building productivity tools, Things proves that constraints create clarity—and that simple is hard.
References
[^1]: Things 3 Apple Design Award. Cultured Code
This post is part of the Design Studies series exploring the design decisions behind exceptional products. See also: Notion, Arc Browser.
For foundational design principles, see the complete design guide.