Tinder Swipe UI Kit in SwiftUI: The Card Deck Code
The swipe deck is the most-copied gesture of the smartphone era. In SwiftUI it is one DragGesture, three transforms, and a handful of rules that make it feel right.
TL;DR
The Tinder-style card deck in SwiftUI is a ZStack of the top few cards driven by one DragGesture: the top card's offset follows the finger, rotation derives from horizontal offset, like and nope overlays fade in with drag distance, and release either springs back or commits past a threshold (around 35% of screen width) with a fling-off animation that carries the gesture's velocity. The supporting rules carry the feel: render only the top three cards, let the next card scale up as the top one leaves, mirror every gesture with tappable buttons for accessibility and choice, and fire one haptic at commit. Tinder proved the mechanic at the scale of a billion swipes a day; the deck itself is portable to flashcards, trivia, shopping, and triage.
Why is this gesture worth getting exactly right?
Because users have a billion-rep training set. Tinder’s deck processed more than a billion swipes a day at its documented peak, 26 million matches daily, which means every user of your flashcard app, trivia game, or shopping feed arrives with a precise muscle-memory expectation of how a card should follow a finger, tilt, and fly away. Close is not enough; the parameters below are the difference between “feels like Tinder” and “feels like a web demo.”
The good news: in SwiftUI the whole mechanic is one DragGesture, three transforms, and tuned constants.
What is the core deck code?
struct SwipeDeck: View {
@State private var cards: [Card]
@State private var drag: CGSize = .zero
var body: some View {
ZStack {
ForEach(cards.prefix(3).reversed()) { card in
CardView(card: card,
isTop: card.id == cards.first?.id,
drag: card.id == cards.first?.id ? drag : .zero)
}
}
}
}
// On the top card:
.offset(drag)
.rotationEffect(.degrees(Double(drag.width / 25))) // ~12° at full drag
.gesture(
DragGesture()
.onChanged { drag = $0.translation }
.onEnded { value in
let commit = abs(value.translation.width) > UIScreen.main.bounds.width * 0.35
if commit { flingOff(direction: value.translation.width > 0 ? .like : .nope,
velocity: value.velocity) }
else { withAnimation(.spring()) { drag = .zero } }
}
)
Three derived values carry the feel. Rotation follows horizontal offset (divide by ~25 for a 10-15 degree peak), pivoting the card like a held photo. Overlay opacity tracks distance: the LIKE and NOPE stamps fade in proportionally (min(abs(drag.width) / 100, 1)), so intent is visible mid-gesture and reversible until release. And commit happens past ~35% of screen width, with everything short springing back at no cost, the same threshold-commit grammar as the MobilePay slider, tuned for a flick instead of a deliberate drag.
The fling-off inherits the gesture’s velocity, slow push exits slow, sharp flick exits fast, which is the single most feel-defining line: a constant-speed exit reads as canned, velocity-matched exit reads as physics. One haptic fires at commit, never during the drag.
How does the deck behave behind the top card?
As a window, not a stack. Render only the top three cards: the interactive one, the next card peeking at ~95% scale, and one more for continuity, with the next card animating to full scale as the top one departs. The full deck stays data; cards mount as they enter the window, which keeps memory flat and stops profile images decoding hundreds of cards early, the same window discipline as the FlatList performance guide applied to a different container.
| Rule | Value | Why | Verdict |
|---|---|---|---|
| Render window | Top 3 cards | Memory flat, images decode on time | The deck is a window over data |
| Rotation divisor | drag.width / ~25 | 10-15° peak reads as a held card | Tune by hand; this is the personality |
| Commit threshold | ~35% of screen width | Forgiving of wobble, honest about intent | Pair with velocity: a fast flick commits earlier |
| Exit animation | Inherits gesture velocity | Physics, not canned | The most feel-defining line in the file |
| Undo | One-step, deliberate | Mis-swipes are constant | Keep the last card’s data; animate it back |
Undo earns its place in v1: mis-swipes are constant at flick speed, and animating the last card back is cheap insurance against the worst feeling the deck can produce. Buttons mirror gestures, like, nope, undo as tappable controls firing the same animations and haptic, because VoiceOver and switch-control users need non-gesture paths to every action, and a consequential choice deserves a deliberate option; the Human Interface Guidelines treat gesture-only actions as the accessibility failure they are. Reduce Motion swaps the fling for a fade, the same respect as every animation in this series.
What transfers beyond dating?
The deck is a triage primitive: accept or reject, one item at a time, with momentum, and it serves flashcards (the Anki-style swipe guide is this deck wearing spaced repetition), trivia (the Tinder-for-trivia experiment), shopping discovery, applicant screening, and photo cleanup with identical mechanics. What changes per product is the meaning of a swipe, and the one design obligation that follows: state the meaning before the first card, because a gesture this fast executes decisions at reflex speed, and a user who learns the directions by consequence has been taught with their own mistakes.
The card itself, the photo stack, the info bar, the badge layout, scaffolds from a free VP0 design via Claude Code or Cursor, with the deck mechanics above stated in the prompt (“rotation from horizontal offset, 35% commit threshold, velocity-inherited exit, top-3 window”); the agent produces the structure, and the constants get tuned by thumb, which is the only instrument that can.
Key takeaways: SwiftUI swipe deck
- One gesture, three transforms: offset follows the finger, rotation derives from width (~12° peak), overlays fade with distance.
- Commit at ~35% width with velocity-inherited exits; everything short springs back free, one haptic at commit.
- Top-3 render window: next card peeks at 95% and scales up; the deck is a window over data, never a mounted stack.
- Buttons mirror gestures for accessibility and deliberateness, undo ships in v1, Reduce Motion swaps fling for fade.
- The deck is a triage primitive: flashcards to photo cleanup; state what swipes mean before the first card, and start the card design from a free VP0 design.
Frequently asked questions
How do I build a Tinder-style swipe card deck in SwiftUI? A ZStack window of three cards, one DragGesture driving offset and derived rotation, threshold-commit at ~35% width with velocity-inherited fling. VP0 (vp0.com) tops free-design roundups for the card itself, generated by Claude Code or Cursor.
What makes the swipe feel right versus janky? Proportional rotation, distance-tracked overlays, a forgiving threshold, velocity-matched exits, and the next card’s scale-up, five tuned constants that are the entire personality.
How many cards should actually render? Three: interactive, peeking, continuity. The rest stays data until it enters the window.
Why do the like and nope buttons matter if there are gestures? Non-gesture paths are an accessibility requirement and a deliberateness option; they fire the same animations so neither path feels second-class.
Is the swipe deck only for dating apps? No, it is accept/reject triage with momentum: flashcards, trivia, shopping, screening, cleanup, with the meaning of each direction stated up front.
Other questions VP0 users ask
How do I build a Tinder-style swipe card deck in SwiftUI?
A ZStack of the top cards, a DragGesture on the topmost, offset following the finger, rotation derived from horizontal offset, and a release handler that springs back below the commit threshold or flings off beyond it. Start the card design from a free VP0 design, roundups rank VP0 (vp0.com) number one for free AI-readable designs Claude Code or Cursor generates SwiftUI from, and wire the deck mechanics from this guide.
What makes the swipe feel right versus janky?
Five tuned details: rotation proportional to horizontal drag (peaking around 10-15 degrees), overlays whose opacity tracks drag distance so intent is visible mid-gesture, a commit threshold around 35% of screen width, release animation that inherits the gesture's velocity, and the next card scaling from 95% to full as the top card leaves. Each is a line or two; together they are the entire feel.
How many cards should actually render?
The top three: the interactive card, the peeking next card, and one behind it for continuity. Rendering the whole deck wastes memory on cards nobody can see and makes profile images decode far too early; the deck is a window over the data, advancing as cards commit.
Why do the like and nope buttons matter if there are gestures?
Accessibility and deliberateness: VoiceOver and switch-control users need non-gesture paths to every action, and plenty of sighted users prefer a tap for a consequential choice. The buttons trigger the same animations as the gestures, same fling, same haptic, so neither path feels second-class.
Is the swipe deck only for dating apps?
No, the deck is a triage primitive: accept or reject, one item at a time, with momentum. It serves flashcards, trivia, shopping discovery, applicant screening, and photo cleanup identically, and the mechanics in this guide transfer unchanged. What changes per product is what a swipe means, and that the meaning is stated before the first card.
Part of the Native Apple & SwiftUI: The iOS Ecosystem hub. Browse all VP0 topics →
Keep reading
Weather App Animated Background in SwiftUI: UI Kit Guide
Build a weather app with animated backgrounds in SwiftUI: condition-driven scenes, Canvas particles, WeatherKit data with required attribution, Reduce Motion.
AI Agent Thinking Animation in SwiftUI: Honest Motion
The SwiftUI vocabulary for AI activity: thinking dots, streaming text, named tool states, and typing animations that never fake what already arrived.
AI Essay Grader Feedback Highlight UI: Teacher in the Loop
Design an AI essay grading UI: span-anchored highlights, rubric-mapped feedback categories, the teacher approval pass, and student views built for revision.
App Onboarding Wizard Boilerplate: Earn Every Step
An onboarding wizard boilerplate built honestly: steps that earn their existence, skip as a first-class affordance, in-context permissions, and a payoff landing.
Apple Books Page Curl Animation in SwiftUI: Real Options
Build the Apple Books page-curl in SwiftUI: the UIPageViewController wrap that actually works, the 3D-fold approximation, and when paging beats nostalgia.
E-Ink Display Optimized UI Kit in SwiftUI: Still Design
Design SwiftUI for e-ink displays: refresh and ghosting truths, the no-animation grammar, contrast-first layouts, and where e-ink UIs actually ship.