-
Notifications
You must be signed in to change notification settings - Fork 0
Primitives
DesignFoundation ships five display primitives — DFBadge, DFAvatar, DFIcon, DFText, and DFDivider — that handle theming, sizing, and accessibility so you never write ad-hoc Circle().fill() or Text("").font(.caption) patterns. This page covers each primitive, its style variants, and how to compose them into rich rows and cells.
Related pages: Getting-Started · Theming · Layout · Lists-Tables-and-Data
A compact label used for counts, status indicators, and short descriptive text.
| Variant | Constructor | Description |
|---|---|---|
| Numeric | DFBadge(count: Int) |
Displays an integer. "99+" when count > 99. |
| Text | DFBadge(text: String) |
Displays any short string. |
| Dot | DFBadge.dot |
A small solid circle. No label. |
| Style | Token | Description |
|---|---|---|
DFFilledBadgeStyle |
.filled |
Solid fill. Default. |
DFOutlinedBadgeStyle |
.outlined |
Transparent with border. |
DFTintedBadgeStyle |
.tinted |
Semi-transparent tint. |
DFGlassBadgeStyle |
.glass |
Material backing. iOS 26+ / macOS 26+. |
Apply with .dfBadgeStyle(_:). Accepts a color parameter for any style to override the default primary tint.
// Unread count
DFBadge(count: 5)
DFBadge(count: 142) // renders "99+"
// Status label
DFBadge(text: "Pro")
DFBadge(text: "New", color: theme.colors.success)
DFBadge(text: "Beta", color: theme.colors.warning)
DFBadge(text: "Deprecated", color: theme.colors.error)
// Presence dot
DFBadge.dot
DFBadge.dot.foregroundColor(theme.colors.success) // active
// Custom color
DFBadge(text: "Starred", color: .purple)
.dfBadgeStyle(.tinted)
// Outlined
DFBadge(count: 3)
.dfBadgeStyle(.outlined)Displays a circular user or entity representation. Falls back gracefully from image URL → initials → generic icon.
// Initials (always available)
DFAvatar(name: "Jamie Lin") // extracts "JL"
DFAvatar(initials: "DF") // explicit string
// Image URL (fallback to initials on failure)
DFAvatar(url: profileURL, name: "Jamie Lin", size: 40)
// SF Symbol (for system or entity icons)
DFAvatar(symbol: "person.fill", size: 40)| Parameter | Type | Default | Description |
|---|---|---|---|
size |
CGFloat |
36 |
Diameter in points. |
badge |
DFPresenceBadge? |
nil |
Small overlay dot indicating presence status. |
strokeColor |
Color? |
nil |
Circular border color. nil means no border. |
strokeWidth |
CGFloat |
2 |
Border width in points. |
public enum DFPresenceBadge: Sendable {
case online, away, busy, offline
}| Style | Token | Description |
|---|---|---|
DFFilledAvatarStyle |
.filled |
Solid background fill. Default. |
DFOutlinedAvatarStyle |
.outlined |
Border only; transparent background. |
DFTintedAvatarStyle |
.tinted |
Semi-transparent tint. |
DFGlassAvatarStyle |
.glass |
Material backing. iOS 26+. |
// Initials fallback
DFAvatar(name: "Alex Nguyen")
// URL with size and presence
DFAvatar(url: user.avatarURL, name: user.displayName, size: 48)
.badge(.online)
// Large with border
DFAvatar(url: profileURL, name: displayName, size: 80)
.strokeColor(theme.colors.primary)
.strokeWidth(3)
// System icon
DFAvatar(symbol: "building.2.fill", size: 40)
.dfAvatarStyle(.tinted)
.foregroundColor(theme.colors.primary)To show a compact group of overlapping avatars:
HStack(spacing: -12) {
ForEach(members.prefix(4)) { member in
DFAvatar(url: member.avatarURL, name: member.name, size: 32)
.strokeColor(theme.colors.background)
.strokeWidth(2)
}
if members.count > 4 {
DFAvatar(initials: "+\(members.count - 4)", size: 32)
.dfAvatarStyle(.tinted)
}
}DFIcon renders an SF Symbol or a custom image at a theme-aware size with consistent foreground color.
// SF Symbol
DFIcon(_ systemName: String)
DFIcon(_ systemName: String, size: DFIconSize)
DFIcon(_ systemName: String, size: DFIconSize, color: Color)
// Custom Image
DFIcon(image: Image)
DFIcon(image: Image, size: DFIconSize)
DFIcon(image: Image, size: DFIconSize, color: Color)| Size | Token | Points |
|---|---|---|
.xs |
theme.components.icon.xsSize |
12 pt |
.sm |
theme.components.icon.smSize |
16 pt |
.md |
theme.components.icon.defaultSize |
20 pt (default) |
.lg |
theme.components.icon.lgSize |
28 pt |
.xl |
theme.components.icon.xlSize |
36 pt |
| Style | Token | Description |
|---|---|---|
DFDefaultIconStyle |
.default |
SF Symbol at natural weight. |
DFCircleIconStyle |
.circle |
Icon inside a filled circle. |
DFFilledIconStyle |
.filled |
Uses .fill variant of the symbol automatically. |
DFIcon is decorative by default (.accessibilityHidden(true)). When the icon carries semantic meaning, pass an accessibilityLabel:
DFIcon("exclamationmark.triangle.fill", color: theme.colors.warning)
.accessibilityLabel("Warning")// Default
DFIcon("star.fill")
// Sized
DFIcon("arrow.up.circle.fill", size: .lg)
// Colored
DFIcon("checkmark.seal.fill", size: .md, color: theme.colors.success)
// Danger icon
DFIcon("xmark.circle.fill", size: .md, color: theme.colors.error)
// Circle container style
DFIcon("lock.fill", size: .sm, color: theme.colors.primary)
.dfIconStyle(.circle)
// Custom image
DFIcon(image: Image("MyCustomLogo"), size: .xl)DFText applies semantic typography tokens from the active theme. Prefer it over bare Text inside DF component hierarchies to ensure typographic consistency.
public init(_ content: String, style: DFTextStyle = .body)| Style | Token | Underlying scale | Role |
|---|---|---|---|
.display |
theme.typography.display |
.largeTitle bold |
Full-screen hero, onboarding heading |
.title |
theme.typography.title |
.title2 semibold |
Screen-level focal element |
.headline |
theme.typography.headline |
.headline semibold |
Section header, card title |
.body |
theme.typography.body |
.body regular |
Primary reading text |
.label |
theme.typography.label |
.subheadline medium |
Row text, toolbar text |
.caption |
theme.typography.caption |
.caption regular |
Metadata, timestamps, badges |
| Style | Token | Description |
|---|---|---|
DFDefaultTextStyle |
.default |
Unstyled — uses token typography only. |
DFPrimaryTextStyle |
.primary |
Applies theme.colors.textPrimary foreground. |
DFSecondaryTextStyle |
.secondary |
Applies theme.colors.textSecondary foreground. |
- Tokens change when the theme changes —
DFTextresponds automatically. - Line spacing and tracking are applied together with the font — raw SwiftUI
Textskips tracking by default. - It participates in the
.dfTextStyle(_:)cascade, letting a container override typographic style for all children.
DFText("Dashboard", style: .title)
DFText("Recent Activity", style: .headline)
DFText("Showing 42 items from the last 30 days.", style: .body)
DFText("Last synced 3 minutes ago", style: .caption)
// Color override
DFText("Optional", style: .caption)
.foregroundColor(theme.colors.textSecondary)
// Full width body paragraph
DFText(article.body, style: .body)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(nil)
// Secondary style cascade
VStack(alignment: .leading) {
DFText("Member since", style: .label)
DFText(memberSinceDate, style: .caption)
}
.dfTextStyle(.secondary)A single-pixel horizontal or vertical separator styled with theme.colors.border.
// Horizontal (default)
DFDivider()
DFDivider(insets: EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 0))
// Vertical
DFDivider(orientation: .vertical)
DFDivider(orientation: .vertical, length: 24)
// Labeled (horizontal)
DFDivider(label: "or")
DFDivider(label: "Continued from above", style: .secondary)| Style | Token | Description |
|---|---|---|
.default |
default |
theme.colors.border at full opacity. |
.subtle |
.subtle |
theme.colors.border at 50% opacity. |
.secondary |
.secondary |
Muted color used for labeled dividers. |
// Between rows
VStack(spacing: 0) {
DFListRow("First item")
DFDivider()
DFListRow("Second item")
DFDivider()
DFListRow("Third item")
}
// With leading inset to align with row content
DFDivider(insets: EdgeInsets(top: 0, leading: 56, bottom: 0, trailing: 0))
// Vertical between columns
HStack(spacing: 16) {
MetricBlock(label: "Posts", value: "142")
DFDivider(orientation: .vertical, length: 40)
MetricBlock(label: "Followers", value: "1.2K")
DFDivider(orientation: .vertical, length: 40)
MetricBlock(label: "Following", value: "88")
}
// Labeled "or" separator in a login form
VStack(spacing: 16) {
DFButton("Continue with Apple") { signInWithApple() }
DFDivider(label: "or")
DFButton("Continue with email") { showEmailForm = true }
.dfButtonStyle(.outlined)
}struct AuthorRow: View {
let author: Author
@Environment(\.dfTheme) private var theme
var body: some View {
HStack(alignment: .top, spacing: theme.spacing.md) {
// Avatar with presence
DFAvatar(url: author.avatarURL, name: author.displayName, size: 44)
.badge(author.isOnline ? .online : .offline)
VStack(alignment: .leading, spacing: theme.spacing.xs) {
// Name + verified badge
HStack(spacing: theme.spacing.xs) {
DFText(author.displayName, style: .headline)
if author.isVerified {
DFIcon("checkmark.seal.fill", size: .sm, color: theme.colors.primary)
.accessibilityLabel("Verified author")
}
Spacer()
DFBadge(text: author.role, color: theme.colors.accent)
.dfBadgeStyle(.tinted)
}
// Handle
DFText("@\(author.handle)", style: .caption)
.foregroundColor(theme.colors.textSecondary)
// Bio
DFText(author.bio, style: .body)
.lineLimit(3)
.padding(.top, theme.spacing.xs)
// Stats
HStack(spacing: theme.spacing.lg) {
HStack(spacing: theme.spacing.xs) {
DFIcon("doc.text.fill", size: .xs, color: theme.colors.textSecondary)
DFText("\(author.postCount) posts", style: .caption)
.foregroundColor(theme.colors.textSecondary)
}
HStack(spacing: theme.spacing.xs) {
DFIcon("person.2.fill", size: .xs, color: theme.colors.textSecondary)
DFText("\(author.followerCount) followers", style: .caption)
.foregroundColor(theme.colors.textSecondary)
}
}
}
}
.padding(theme.spacing.lg)
.background(theme.colors.surface)
.cornerRadius(theme.radius.lg)
}
}See also: Layout · Lists-Tables-and-Data · Theming · Getting-Started