-
Notifications
You must be signed in to change notification settings - Fork 0
Layout
This page covers DFCard, DesignFoundation's primary layout container. For navigation containers, see Navigation. For list components, see Lists-Tables-and-Data. For spacing tokens and surface colors referenced throughout this page, see Theming.
DFCard is a themed, interactive-capable surface container. It wraps arbitrary View content inside a styled background, handles press animations, respects the active theme, and participates in the SwiftUI accessibility tree automatically.
import DesignFoundationpublic struct DFCard<Content: View>: View {
public init(
action: (() -> Void)? = nil,
@ViewBuilder content: () -> Content
)
}| Parameter | Type | Default | Description |
|---|---|---|---|
action |
(() -> Void)? |
nil |
Optional tap handler. When provided, the card becomes interactive: it gains .isButton accessibility trait and plays a press-scale animation on tap. |
content |
@ViewBuilder () -> Content |
— | Any SwiftUI view hierarchy displayed inside the card surface. |
When action is nil the card is a passive container — no tap handling, no press animation, and no .isButton accessibility trait.
When action is provided DFCard tracks a drag gesture internally to drive a 0.98 scale press effect. This animation is automatically suppressed when accessibilityReduceMotion is enabled.
Padding and corner radius are owned by the active card style and resolved in this order:
-
theme.components.card.padding— per-component token override -
theme.spacing.lg(16 pt) — fallback from the spacing scale
Corner radius resolves identically:
theme.components.card.cornerRadius-
theme.radius.lg— fallback
Apply with .dfCardStyle(_:). The modifier cascades — set it on a parent container to style all descendant cards unless overridden closer to the leaf.
| Style | Static accessor | Background | Shadow | Border | Availability |
|---|---|---|---|---|---|
| Elevated | .elevated |
theme.colors.surface |
theme.shadows.sm |
None | iOS 18+, macOS 15+ |
| Outlined | .outlined |
theme.colors.surface |
None |
theme.colors.border 1 pt stroke |
iOS 18+, macOS 15+ |
| Filled | .filled |
theme.colors.surfaceElevated |
None | None | iOS 18+, macOS 15+ |
| Glass | .glass |
.regularMaterial |
None |
white.opacity(0.2) 0.5 pt |
iOS 26+, macOS 26+ |
Elevated is the default when no style is explicitly applied.
Outlined replaces the shadow with a 1-point theme.colors.border stroke. Use when the background is already elevated.
Filled uses theme.colors.surfaceElevated — useful for secondary panels or cards nested inside an already-elevated container.
Glass applies SwiftUI's .regularMaterial background and a translucent white hairline border. Requires iOS 26 or macOS 26.
Conform to DFCardStyle and annotate as Sendable:
public protocol DFCardStyle {
associatedtype Body: View
@ViewBuilder func makeBody(configuration: DFCardStyleConfiguration) -> Body
}DFCardStyleConfiguration gives you everything you need:
| Property | Type | Notes |
|---|---|---|
content |
AnyView |
The card's child view hierarchy |
isPressed |
Bool |
true while the user's finger is down |
isDisabled |
Bool |
Mirrors @Environment(\.isEnabled)
|
isInteractive |
Bool |
true when the card has an action closure |
theme |
DFTheme |
The active theme |
struct DashedCardStyle: DFCardStyle, Sendable {
func makeBody(configuration: DFCardStyleConfiguration) -> some View {
let theme = configuration.theme
configuration.content
.padding(theme.spacing.lg)
.background(theme.colors.surface)
.clipShape(RoundedRectangle(cornerRadius: theme.radius.md))
.overlay(
RoundedRectangle(cornerRadius: theme.radius.md)
.stroke(theme.colors.border, style: StrokeStyle(lineWidth: 1.5, dash: [6]))
)
.opacity(configuration.isDisabled ? 0.5 : 1.0)
}
}
DFCard { Text("Drop files here") }
.dfCardStyle(DashedCardStyle())DFCard wraps its content in .accessibilityElement(children: .contain), so all child elements remain individually focusable. When an action is provided, .isButton is added to the card's own accessibility traits.
Press animations automatically deactivate when the system accessibilityReduceMotion preference is on.
DFCard {
VStack(alignment: .leading, spacing: theme.spacing.sm) {
DFText("What is SwiftUI?", style: .headline)
DFText(
"SwiftUI is Apple's declarative UI framework for building apps across all Apple platforms.",
style: .body
)
}
}DFCard(action: { navigateToProfile(user) }) {
HStack(spacing: theme.spacing.md) {
DFAvatar(url: user.avatarURL, size: 48)
VStack(alignment: .leading, spacing: theme.spacing.xs) {
DFText(user.displayName, style: .headline)
DFText(user.role, style: .caption)
.foregroundColor(theme.colors.textSecondary)
}
Spacer()
DFIcon("chevron.right", color: theme.colors.textSecondary)
}
}
.accessibilityLabel("\(user.displayName), \(user.role)")DFCard {
VStack(alignment: .leading, spacing: 0) {
DFText("Notifications", style: .subheadline)
.foregroundColor(theme.colors.textSecondary)
.padding(.bottom, theme.spacing.sm)
DFToggle("Push notifications", isOn: $pushEnabled)
DFDivider()
DFToggle("Email digest", isOn: $emailEnabled)
DFDivider()
DFToggle("In-app sounds", isOn: $soundsEnabled)
}
}
.dfCardStyle(.outlined)@Environment(\.dfTheme) private var theme
DFCard {
VStack(alignment: .leading, spacing: theme.spacing.sm) {
HStack(alignment: .top) {
DFText("Monthly Revenue", style: .caption)
.foregroundColor(theme.colors.textSecondary)
Spacer()
DFBadge(text: "+12%", color: theme.colors.success)
}
DFText("$48,200", style: .largeTitle)
.foregroundColor(theme.colors.textPrimary)
HStack(spacing: theme.spacing.xs) {
DFIcon("arrow.up.right", size: .sm, color: theme.colors.success)
DFText("vs last month", style: .caption)
.foregroundColor(theme.colors.textSecondary)
}
}
}DFCard {
VStack(alignment: .leading, spacing: theme.spacing.md) {
DFText("Recent Deployments", style: .headline)
ForEach(deployments) { deployment in
DFCard(action: { openDeployment(deployment) }) {
HStack(spacing: theme.spacing.md) {
DFIcon(
deployment.status == .success ? "checkmark.seal.fill" : "xmark.seal.fill",
color: deployment.status == .success
? theme.colors.success
: theme.colors.error
)
VStack(alignment: .leading, spacing: theme.spacing.xs) {
DFText(deployment.commitMessage, style: .subheadline)
DFText(deployment.relativeDate, style: .caption)
.foregroundColor(theme.colors.textSecondary)
}
Spacer()
DFBadge(
text: deployment.status.rawValue,
color: deployment.status == .success
? theme.colors.success
: theme.colors.error
)
}
}
.dfCardStyle(.elevated)
}
}
}
.dfCardStyle(.filled)ScrollView {
LazyVStack(spacing: theme.spacing.sm) {
ForEach(items) { item in
DFCard(action: { select(item) }) {
DFListRow(item.title, subtitle: item.subtitle, icon: item.icon)
}
}
}
.padding(theme.spacing.lg)
}DFCard {
VStack(alignment: .leading, spacing: theme.spacing.sm) {
DFSkeleton(width: 40, height: 40, shape: .circle)
DFSkeleton(width: 160, height: 14)
DFSkeleton(width: 220, height: 12)
DFSkeleton(width: 190, height: 12)
}
}Swap the skeleton content for real data once your async load completes. Keep the card in the hierarchy to prevent layout jumps.
- Theming — surface colors, spacing scale, radius tokens, and theme presets
-
Lists-Tables-and-Data —
DFList,DFListRow,DFTable,DFDataGrid -
Primitives —
DFText,DFBadge,DFAvatar,DFIcon,DFDivider - Getting-Started — installation, theme setup, and first component
If you need production-ready dashboard stat cards, metric grids, activity feed cards, or full content layouts, DesignFoundation Pro ships these ready to drop in. See https://nerdsnipe-inc.github.io/design-foundation/pro/ for details.