SwiftUI Page Indicator Component

Page dots where the active one stretches into a capsule instead of just changing colour. Tappable, so it doubles as navigation.

Page 1

Variants: Dots, Bars

Install the free SwiftUI page indicator

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

Usage

Usage.swift
SKPageIndicator(count: 4, index: $page)
SKPageIndicator(count: 4, index: $page, style: .bars)

Full source · 102 lines · MIT

SKPageIndicator.swift
//
//  SKPageIndicator.swift
//  Swift Kit UI
//
//  Requires: SwiftKitUI.swift
//
//  Page dots where the active one stretches into a capsule instead of just
//  changing colour. Tappable, so it doubles as navigation.
//
//  Usage:
//      SKPageIndicator(count: 4, index: $page)
//      SKPageIndicator(count: 4, index: $page, style: .bars)
//

import SwiftUI

public struct SKPageIndicator: View {
    public enum Style: Sendable { case dots, bars }

    private let count: Int
    @Binding private var index: Int
    private let style: Style
    private let tint: Color

    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    public init(
        count: Int,
        index: Binding<Int>,
        style: Style = .dots,
        tint: Color = SK.Palette.accent
    ) {
        self.count = count
        self._index = index
        self.style = style
        self.tint = tint
    }

    public var body: some View {
        HStack(spacing: style == .dots ? SK.Space.sm : SK.Space.xs) {
            ForEach(0..<max(count, 1), id: \.self) { page in
                dot(page)
            }
        }
        .accessibilityElement()
        .accessibilityLabel("Page")
        .accessibilityValue("\(index + 1) of \(count)")
        .accessibilityAdjustableAction { direction in
            switch direction {
            case .increment: if index + 1 < count { index += 1 }
            case .decrement: if index > 0 { index -= 1 }
            @unknown default: break
            }
        }
    }

    private func dot(_ page: Int) -> some View {
        let active = page == index
        let height: CGFloat = style == .dots ? 8 : 4
        let activeWidth: CGFloat = style == .dots ? 24 : 28
        let restWidth: CGFloat = style == .dots ? 8 : 14

        return Capsule()
            .fill(active ? tint : SK.Palette.separator)
            .frame(width: active ? activeWidth : restWidth, height: height)
            // Hit target stays a comfortable size even though the dot is tiny.
            .frame(width: max(activeWidth, 30), height: SK.Space.xxl, alignment: .center)
            .contentShape(.rect)
            .onTapGesture {
                guard !active else { return }
                withAnimation(SKMotion.snap.resolved(reduceMotion: reduceMotion)) { index = page }
                SK.Haptic.play(.selection)
            }
            .skAnimation(.snap, value: index)
    }
}

#Preview("SKPageIndicator") {
    @Previewable @State var page = 0

    VStack(spacing: SK.Space.xxl) {
        TabView(selection: $page) {
            ForEach(0..<4, id: \.self) { i in
                RoundedRectangle(cornerRadius: SK.Radius.xl, style: .continuous)
                    .fill(SK.Palette.accentSoft)
                    .overlay(Text("Page \(i + 1)").font(SK.Type_.title))
                    .padding(SK.Space.lg)
                    .tag(i)
            }
        }
        #if os(iOS)
        .tabViewStyle(.page(indexDisplayMode: .never))
        #endif
        .frame(height: 220)

        SKPageIndicator(count: 4, index: $page)
        SKPageIndicator(count: 4, index: $page, style: .bars, tint: SK.Palette.success)
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(SK.Palette.surfaceSunken)
}

Questions

Is this SwiftUI page indicator 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?
SKPageIndicator.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.