SwiftUI Card View: A Reusable Card Component with a Custom CardStyle
SwiftUI has no Card type, so you build one — a rounded, shadowed container you can reuse everywhere. This guide covers backgrounds, clipping, a CardStyle ViewModifier, making cards tappable with NavigationLink, and iOS 26 Liquid Glass cards. Real Swift 6 code.
SwiftUI has no built-in Card, so wrap content in padding, add a rounded background with .background(_:in:), and clip with .clipShape(RoundedRectangle(cornerRadius:)). Package the look as a ViewModifier (a CardStyle) and expose it via a View extension so every card stays consistent. Make cards tappable by wrapping them in a NavigationLink, and on iOS 26 use .glassEffect() for Liquid Glass cards.
There is no Card — you compose one
Unlike UIKit's absence of a card control, SwiftUI's answer is composition: a card is just content with padding, a rounded background, and a shadow. Start by padding your content, then apply a background using the shape-based overload .background(_:in:) which fills a RoundedRectangle behind the content in one call. Add a subtle shadow to lift the surface off the page. The order of modifiers matters — apply padding before the background so the fill wraps the padded content, not just the text. Getting this base right once is what lets you promote it into a reusable style in the next step rather than copy-pasting the same three modifiers everywhere.
struct InlineCard: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Weekly Summary").font(.headline)
Text("You completed 12 of 15 tasks.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding()
.background(Color(.secondarySystemBackground),
in: RoundedRectangle(cornerRadius: 16))
.shadow(color: .black.opacity(0.08), radius: 8, y: 4)
}
}clipShape and why it matters
The shape-based .background(_:in:) clips the fill to the rounded rectangle for you, but the moment a card contains an image that should reach the edges — a photo header, a colored banner — you need .clipShape on the whole card so the image corners round too. Apply .clipShape(RoundedRectangle(cornerRadius:)) to the container after laying out content; anything overflowing the shape gets masked. A classic bug is a card with rounded corners but a square photo poking out the top: that's a missing clipShape on the outer container. If you also want a border, add an .overlay with a stroked RoundedRectangle of the same radius so the outline follows the clip exactly.
struct PhotoCard: View {
let imageName: String
let title: String
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Image(imageName)
.resizable().scaledToFill()
.frame(height: 140)
Text(title).font(.headline).padding()
}
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 16))
.overlay(RoundedRectangle(cornerRadius: 16)
.stroke(.black.opacity(0.06), lineWidth: 1))
}
}A reusable CardStyle ViewModifier
Copy-pasting padding, background, clip, and shadow across dozens of views is how a design drifts. Package the look as a ViewModifier and expose it through a View extension so applying a card is a single .cardStyle() call. Centralizing it means changing the corner radius or shadow in one place updates every card in the app — the whole point of a design system. Parameterize the modifier if you need variants (a compact card, an elevated card) by adding stored properties. This is also where you'd reference shared tokens for surface color and radius so cards match buttons, sheets, and list rows without anyone eyeballing values.
struct CardStyle: ViewModifier {
var cornerRadius: CGFloat = 16
func body(content: Content) -> some View {
content
.padding()
.background(Color(.secondarySystemBackground),
in: RoundedRectangle(cornerRadius: cornerRadius))
.shadow(color: .black.opacity(0.08), radius: 8, y: 4)
}
}
extension View {
func cardStyle(cornerRadius: CGFloat = 16) -> some View {
modifier(CardStyle(cornerRadius: cornerRadius))
}
}
// Usage:
// VStack { /* content */ }.cardStyle()iOS 26 Liquid Glass cards
On iOS 26, cards can adopt the Liquid Glass material with the .glassEffect() modifier, which renders a translucent, refractive surface that picks up the content behind it — ideal for cards floating over imagery or a colorful background. Apply it in place of an opaque fill; you can pass a shape so the glass follows your rounded rectangle, and combine cards in a GlassEffectContainer to let adjacent glass elements blend naturally. Provide a graceful fallback for iOS 16–25 by branching on availability and using your material or solid background there. Keep text legible — glass over a busy backdrop can reduce contrast, so lean on vibrant foreground styles or a subtle scrim behind the text.
- .glassEffect(in:) applies Liquid Glass clipped to your card shape
- Group related glass cards in a GlassEffectContainer to blend
- Branch on #available so iOS 16–25 fall back to a material
- Watch contrast: glass over busy imagery can wash out text
struct GlassCard<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
let shape = RoundedRectangle(cornerRadius: 20)
Group {
if #available(iOS 26, *) {
content.padding().glassEffect(in: shape)
} else {
content.padding()
.background(.ultraThinMaterial, in: shape)
}
}
}
}Ship your SwiftUI app in 5 emails
A free 5-part course on the parts that actually stall launches: paywall, auth, onboarding, App Store review, and pricing. No fluff, unsubscribe anytime.
A card component wired to your theme
The Swift Kit ships a themeable Card tied to design-system tokens for radius, shadow, and surface color — plus a Liquid Glass variant that falls back gracefully on older iOS.
Rather have it done for you? I set up and ship your app from $499. See how
How to build a reusable card component
Promote the card look into a ViewModifier so every card in the app stays consistent and updates in one place.
- 1
Compose the base
Pad content, add a rounded background, and a subtle shadow.
content.padding().background(surface, in: RoundedRectangle(cornerRadius: 16)) - 2
Wrap in a ViewModifier
Move those modifiers into a CardStyle: ViewModifier body.
struct CardStyle: ViewModifier { func body(content: Content) -> some View { /* ... */ } } - 3
Expose an extension
Add a View extension so applying it reads as one modifier.
extension View { func cardStyle() -> some View { modifier(CardStyle()) } } - 4
Make it tappable
Wrap the styled card in a NavigationLink with .buttonStyle(.plain).
NavigationLink(value: item) { CardBody().cardStyle() }.buttonStyle(.plain)
Frequently Asked Questions
Why does the image inside my SwiftUI card poke out past the rounded corners?
How do I make a whole SwiftUI card tappable for navigation without it turning blue?
What's the cleanest way to reuse the same SwiftUI card look across many screens?
How do I apply the iOS 26 Liquid Glass effect to a SwiftUI card and still support older iOS?
Why does my SwiftUI card background not wrap the padding around the content?
Keep exploring
A design system, not a pile of modifiers
The Swift Kit is a $99 one-time SwiftUI boilerplate with a token-driven design system, reusable cards, auth, and paywalls — so your UI stays consistent from the first screen to the hundredth.
Get The Swift Kit — $99One-time purchase · Lifetime updates · 14-day refund