SwiftUI Carousel Component
A snapping card carousel. Off-centre cards shrink and dim as they move away, which gives the row depth and makes the focused card unambiguous.
Components
Animations
Screens
Install the free SwiftUI carousel
- Add SwiftKitUI.swift to your app once. It holds the tokens and motion curves.
- Copy SKCarousel.swift below into your project. It needs nothing else.
Usage
Usage.swift
SKCarousel(items: photos) { photo in PhotoCard(photo) }Full source · 118 lines · MIT
SKCarousel.swift
//
// SKCarousel.swift
// Swift Kit UI
//
// Requires: SwiftKitUI.swift
//
// A snapping card carousel. Off-centre cards shrink and dim as they move away,
// which gives the row depth and makes the focused card unambiguous.
//
// The depth effect uses `.scrollTransition`, so it is driven by real scroll
// position every frame rather than by a selected index — the cards respond
// continuously while your finger is still down.
//
// Usage:
// SKCarousel(items: photos) { photo in PhotoCard(photo) }
//
import SwiftUI
public struct SKCarousel<Item: Identifiable, Content: View>: View {
private let items: [Item]
private let cardWidth: CGFloat
private let height: CGFloat
private let content: (Item) -> Content
@State private var current: Item.ID?
public init(
items: [Item],
cardWidth: CGFloat = 260,
height: CGFloat = 320,
@ViewBuilder content: @escaping (Item) -> Content
) {
self.items = items
self.cardWidth = cardWidth
self.height = height
self.content = content
}
public var body: some View {
VStack(spacing: SK.Space.lg) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: SK.Space.lg) {
ForEach(items) { item in
content(item)
.frame(width: cardWidth, height: height)
.scrollTransition(axis: .horizontal) { view, phase in
view
.scaleEffect(phase.isIdentity ? 1 : 0.88)
.opacity(phase.isIdentity ? 1 : 0.55)
.rotation3DEffect(
.degrees(phase.value * -8),
axis: (x: 0, y: 1, z: 0),
perspective: 0.5
)
}
.id(item.id)
}
}
.scrollTargetLayout()
.padding(.horizontal, SK.Space.xl)
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $current, anchor: .center)
indicator
}
.onAppear { current = items.first?.id }
.onChange(of: current) { _, _ in SK.Haptic.play(.light) }
}
private var indicator: some View {
HStack(spacing: SK.Space.xs) {
ForEach(items) { item in
let active = item.id == current
Capsule()
.fill(active ? SK.Palette.accent : SK.Palette.separator)
.frame(width: active ? 20 : 6, height: 6)
.skAnimation(.snap, value: current)
}
}
.accessibilityHidden(true)
}
}
private struct DemoCard: Identifiable {
let id: Int
let title: String
let tint: Color
}
#Preview("SKCarousel") {
let cards = [
DemoCard(id: 0, title: "Components", tint: .purple),
DemoCard(id: 1, title: "Animations", tint: .blue),
DemoCard(id: 2, title: "Screens", tint: .teal),
DemoCard(id: 3, title: "Liquid Glass", tint: .indigo),
]
return SKCarousel(items: cards) { card in
VStack(alignment: .leading) {
Spacer()
Text(card.title)
.font(SK.Type_.title)
.foregroundStyle(.white)
}
.padding(SK.Space.xl)
.frame(maxWidth: .infinity, alignment: .leading)
.background {
LinearGradient(colors: [card.tint, card.tint.opacity(0.55)],
startPoint: .topLeading, endPoint: .bottomTrailing)
}
.clipShape(RoundedRectangle(cornerRadius: SK.Radius.xl, style: .continuous))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(SK.Palette.surfaceSunken)
}
Questions
- Is this SwiftUI carousel free for commercial apps?
- Yes. Swift Kit UI is MIT licensed, so you can use it in personal and commercial apps with no payment or sign-up. Keep the licence notice with the code.
- What does it depend on?
- SKCarousel.swift depends only on SwiftKitUI.swift, a single foundation file with the design tokens and motion curves. There are no third-party packages.
- Which iOS versions does it support?
- iOS 17 and later, plus macOS 14.
Related components
Building a whole app? The Swift Kit adds auth, payments and onboarding, ready to ship.