data-display/DSCard

DSCard

data-display
since v1.0

Content container with 4 visual variants, large border radius, and interactive hover support. Generic over any View as content.

iOS 17+macOS 14+

Purpose

Groups related content with clear visual hierarchy. The foundation for most grid and feed layouts.

Variants

Elevated

.elevated

Shadow and white/dark background. The default for most cases.

Outlined

.outlined

Border only, no shadow. Good for contexts that are already elevated.

Filled

.filled

Lightly tinted background (secondaryMuted).

Ghost

.ghost

No border and no shadow. Layout only.

States

default

Resting state.

hover

When isInteractive: true, scales 1.01× with shadow md.

Props

PropTypeDefaultDescription
variantDSCardVariant.elevatedVisual style.
paddingCGFloatDSSpacing.mdInternal padding.
isInteractiveBoolfalseEnables hover effect.
action(() -> Void)?nilMakes the card tappable.
contentreqViewBuilderInternal content.

Examples

Simple card

Static content in an elevated card.

swift
DSCard {
    VStack(alignment: .leading, spacing: DSSpacing.sm) {
        Text("Title")
            .font(.system(size: 17, weight: .semibold))
        Text("Card description")
            .font(.system(size: 14))
            .foregroundStyle(DSColor.muted)
    }
}

Interactive card

Tappable card with hover effect.

swift
DSCard(variant: .elevated, isInteractive: true, action: {
    navigate(to: .projectDetail)
}) {
    VStack(alignment: .leading, spacing: DSSpacing.sm) {
        HStack {
            Text(project.name)
                .font(.system(size: 16, weight: .semibold))
            Spacer()
            DSBadge(text: project.status, variant: .soft)
        }
        Text(project.description)
            .font(.system(size: 13))
            .foregroundStyle(DSColor.muted)
    }
}

Composition: Profile card

Card with avatar, text, and action button.

swift
DSCard(variant: .outlined, padding: DSSpacing.lg) {
    HStack(spacing: DSSpacing.md) {
        DSAvatar(initials: user.initials, size: .lg)
        
        VStack(alignment: .leading, spacing: 4) {
            Text(user.name)
                .font(.system(size: 16, weight: .semibold))
            Text(user.role)
                .font(.system(size: 13))
                .foregroundStyle(DSColor.muted)
        }
        
        Spacer()
        
        DSButton("Follow", variant: .primary, size: .sm) {
            followUser()
        }
    }
}

Usage Guidelines

  • Avoid nesting DSCards. Use variant: .ghost for the inner card.
  • Interactive cards must always have an accessibilityLabel.

When to Use

✓ Use DSCard for:

  • Grouping related content in grids or feeds
  • Interactive list items that navigate to detail screens
  • Feature highlights or product cards
  • Dashboard widgets and metric displays

✗ Avoid using for:

  • Full-screen content (use DSContainer or plain VStack)
  • Single text labels (use DSBadge or DSTag)
  • List rows with standard iOS styling (use DSListRow)

Consider instead:

DSContainer

For full-width content sections without card styling

DSListRow

For iOS-style list items with disclosure indicators

DSFeatureCard

For marketing feature highlights with icons

Accessibility

VoiceOver

Announces content and 'Card' trait. Interactive cards announce 'Button, Card'

Keyboard

  • Space or Return to activate (if interactive)
  • Tab to focus

Dynamic Type

✓ Supported

Contrast

WCAG AA compliant (border 3:1 minimum)

Traits

.isButton when action is setChildren content preserved

Related Components