SwiftUI Section Component

A titled group. Pair with SKRow, or put anything else inside.

A
Aster Coffee
Today · 08:12
−$4.80
N
North Yard
Today · 11:47
−$23.15
P
Pico Transit
Yesterday
−$2.40

Variants: Bordered, Elevated

Install the free SwiftUI section

  1. Add SwiftKitUI.swift to your app once. It holds the tokens and motion curves.
  2. Copy SKSection.swift below into your project. It needs nothing else.

Usage

Usage.swift
SKSection("Preferences") {
SKRow(title: "Wi-Fi")
SKRow(title: "Bluetooth", showsSeparator: false)
}

Full source · 108 lines · MIT

SKSection.swift
//
//  SKSection.swift
//  Swift Kit UI
//
//  Requires: SwiftKitUI.swift
//
//  A titled group. Pair with SKRow, or put anything else inside.
//
//  Deliberately generic over its content rather than taking a row array: a
//  section should not know what a row is. That keeps this file compilable on
//  its own, which is the rule every component in this kit follows.
//
//  Usage:
//      SKSection("Preferences") {
//          SKRow(title: "Wi-Fi")
//          SKRow(title: "Bluetooth", showsSeparator: false)
//      }
//

import SwiftUI

public struct SKSection<Content: View>: View {
    private let title: String?
    private let footer: String?
    private let style: SK.Surface
    private let content: Content

    public init(
        _ title: String? = nil,
        footer: String? = nil,
        style: SK.Surface = .bordered,
        @ViewBuilder content: () -> Content
    ) {
        self.title = title
        self.footer = footer
        self.style = style
        self.content = content()
    }

    public var body: some View {
        VStack(alignment: .leading, spacing: SK.Space.sm) {
            if let title {
                Text(title.uppercased())
                    .font(SK.Type_.caption)
                    .foregroundStyle(SK.Palette.labelSecondary)
                    .padding(.leading, SK.Space.xs)
                    .accessibilityAddTraits(.isHeader)
            }

            VStack(spacing: 0) { content }
                .skSurface(style, radius: SK.Radius.lg)
                // Clip so a trailing row separator cannot escape the corner
                // radius and draw a stray line across the card's rounded edge.
                .clipShape(RoundedRectangle(cornerRadius: SK.Radius.lg, style: .continuous))

            if let footer {
                Text(footer)
                    .font(SK.Type_.caption)
                    .foregroundStyle(SK.Palette.labelTertiary)
                    .padding(.leading, SK.Space.xs)
                    .fixedSize(horizontal: false, vertical: true)
            }
        }
    }
}

#Preview("SKSection") {
    // Rows here are inlined rather than using SKRow: a component's preview must
    // not reference a sibling, or the file stops compiling on its own.
    // `scripts/isolation_test.py` enforces this.
    func demoRow(_ title: String, _ symbol: String, last: Bool = false) -> some View {
        VStack(spacing: 0) {
            HStack(spacing: SK.Space.md) {
                Image(systemName: symbol)
                    .font(SK.Icon.symbol(SK.Icon.md))
                    .foregroundStyle(SK.Palette.accent)
                    .frame(width: SK.Icon.lg)
                Text(title).font(SK.Type_.body).foregroundStyle(SK.Palette.label)
                Spacer()
            }
            .padding(.horizontal, SK.Space.lg)
            .padding(.vertical, SK.Space.md)
            .frame(minHeight: SK.Icon.hitTarget)

            if !last {
                Rectangle()
                    .fill(SK.Palette.separator)
                    .frame(height: SK.Stroke.hairline)
                    .padding(.leading, SK.Space.lg + SK.Icon.lg + SK.Space.md)
            }
        }
    }

    return ScrollView {
        VStack(spacing: SK.Space.xl) {
            SKSection("Preferences") {
                demoRow("Wi-Fi", "wifi")
                demoRow("Bluetooth", "dot.radiowaves.right", last: true)
            }
            SKSection("Danger Zone", footer: "This cannot be undone.") {
                demoRow("Delete Account", "trash.fill", last: true)
            }
        }
        .padding(SK.Space.lg)
    }
    .background(SK.Palette.surfaceSunken)
}

Questions

Is this SwiftUI section free for commercial apps?
Yes. Swift Kit UI is MIT licensed, so you can use it in personal and commercial apps with no payment or sign-up. Keep the licence notice with the code.
What does it depend on?
SKSection.swift depends only on SwiftKitUI.swift, a single foundation file with the design tokens and motion curves. There are no third-party packages.
Which iOS versions does it support?
iOS 17 and later, plus macOS 14.

Related components

Building a whole app? The Swift Kit adds auth, payments and onboarding, ready to ship.