Skip to content

Primitives

Daniel S edited this page Jul 2, 2026 · 1 revision

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


DFBadge

A compact label used for counts, status indicators, and short descriptive text.

Variants

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.

Styles

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.

Examples

// 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)

DFAvatar

Displays a circular user or entity representation. Falls back gracefully from image URL → initials → generic icon.

Sources

// 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)

Parameters

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
}

Styles

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+.

Examples

// 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)

Avatar Stack

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

DFIcon renders an SF Symbol or a custom image at a theme-aware size with consistent foreground color.

Initializers

// 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 Reference

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

Styles

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.

Accessibility

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")

Examples

// 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

DFText applies semantic typography tokens from the active theme. Prefer it over bare Text inside DF component hierarchies to ensure typographic consistency.

Initializer

public init(_ content: String, style: DFTextStyle = .body)

Text Styles

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

Styles

Style Token Description
DFDefaultTextStyle .default Unstyled — uses token typography only.
DFPrimaryTextStyle .primary Applies theme.colors.textPrimary foreground.
DFSecondaryTextStyle .secondary Applies theme.colors.textSecondary foreground.

Why Use DFText Instead of SwiftUI Text?

  1. Tokens change when the theme changes — DFText responds automatically.
  2. Line spacing and tracking are applied together with the font — raw SwiftUI Text skips tracking by default.
  3. It participates in the .dfTextStyle(_:) cascade, letting a container override typographic style for all children.

Examples

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)

DFDivider

A single-pixel horizontal or vertical separator styled with theme.colors.border.

Initializers

// 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)

Styles

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.

Examples

// 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)
}

Composing All Five — Author Bio Row

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

Clone this wiki locally