Card is a composite layout component that combines a media image, a structured header (title, subtitle, icons, a detail image, KPI, action buttons), an optional content body, and a footer with primary and secondary actions.
All fields are optional except title. The component renders only the sections for which you supply content.
Parameters
title
any View / AttributedString
required
Main heading of the card.
Full-width image rendered at the top of the card.
description
any View / AttributedString?
Short description text displayed above the title.
subtitle
any View / AttributedString?
Secondary label below the title.
Row of icons in the card header.
Small image (for example, an avatar) placed in the header alongside the title.
Button rendered at the trailing end of the header.
counter
any View / AttributedString?
Counter text shown in the header (for example, “1 of 3”).
First custom content row inside the card header area.
Second custom content row.
Third custom content row.
KPI value displayed in a dedicated area of the card header.
kpiCaption
any View / AttributedString?
Caption for the KPI value.
Arbitrary content rendered below the header. Use this for DataTable views, tag lists, or other custom content.
Primary action button in the card footer.
Secondary action button in the footer.
Tertiary action button in the footer.
Overflow button (defaults to an ellipsis icon).
Flexible content area whose position is controlled by flexItemPosition.
Where the flex item appears: .aboveMainHeader, .aboveTitle, .betweenTitleAndSubtitle, or .belowSubtitle.
ViewBuilder initializer
Card {
Image("productThumbnail")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 145)
} description: {
Text("SAP BTP")
} title: {
Text("Cloud Integration Suite")
} subtitle: {
Text("Integration & API Management platform")
} icons: {
IconStack(icons: [
.icon(Image(systemName: "circle.fill")),
.icon(Image(systemName: "paperclip")),
.text("3")
])
} headerAction: {
FioriButton(title: "Edit")
} counter: {
Text("1 of 5")
} kpi: {
_KPIItem(KPIItemData.components([
.unit("$"),
.metric("26.9"),
.unit("M")
]))
} kpiCaption: {
Text("Revenue")
} cardBody: {
VStack(alignment: .leading, spacing: 8) {
DataTable(model: summaryTableModel)
.frame(height: 80)
Divider()
HStack {
Tag(verbatim: "Cloud")
Tag(verbatim: "Integration")
}
}
} action: {
FioriButton(title: "Approve")
} secondaryAction: {
FioriButton(title: "Decline")
}
Type-based initializer
For cards backed by data models, the type-based initializer is more concise:
Card(
mediaImage: Image("productThumbnail"),
description: "SAP BTP",
title: "Cloud Integration Suite",
subtitle: "Integration & API Management platform",
icons: [
.icon(Image(systemName: "circle.fill")),
.icon(Image(systemName: "paperclip"))
],
headerAction: FioriButton(title: "Edit"),
counter: "1 of 5",
kpi: KPIItemData.components([.unit("$"), .metric("26.9"), .unit("M")]),
kpiCaption: "Revenue",
action: FioriButton(title: "Approve"),
secondaryAction: FioriButton(title: "Decline")
)
Example: product card
struct ProductCard: View {
let product: Product
var body: some View {
Card(
mediaImage: product.thumbnailImage,
title: AttributedString(product.name),
subtitle: AttributedString(product.categoryName),
icons: [
.icon(Image(systemName: product.inStock ? "checkmark.circle.fill" : "xmark.circle"))
],
detailImage: Image("brand_logo"),
kpi: KPIItemData.components([
.unit(product.currency),
.metric(product.formattedPrice)
]),
kpiCaption: "Unit price",
action: FioriButton(title: "Add to cart") { _ in
cart.add(product)
},
secondaryAction: FioriButton(title: "Save") { _ in
wishlist.add(product)
}
)
.padding(.horizontal)
}
}
Sub-component styling
All named sections of Card — mediaImage, title, subtitle, kpi, cardBody, action, and so on — have corresponding style modifiers that let you override the default Fiori appearance.
Card(title: "Dashboard", subtitle: "Weekly summary")
.titleStyle { config in
config.title
.font(.fiori(forTextStyle: .title2))
.foregroundStyle(Color.preferredColor(.primaryLabel))
}
.actionStyle { config in
config.action
.fioriButtonStyle(.borderedProminent)
}