SwiftUI Citation Chip Component
An inline source marker sized to sit inside a sentence without breaking its rhythm, with a fixed minimum width so one- and two-digit citations do not jitter the line. Tapping opens a popover rather than pushing a screen, and stays a popover on iPhone.
Annual plans now account for 61% of new subscriptions1, up from 44% in Q22.
openviewpartners.com
SaaS Benchmarks 2026
Variants: Numbered, Domain
Install the free SwiftUI citation chip
- Add SwiftKitUI.swift to your app once. It holds the tokens and motion curves.
- Copy SKCitationChip.swift below into your project. It needs nothing else.
Usage
Usage.swift
SKCitationChip(index: 1, source: source)
SKCitationChip(source: source, style: .domain)Full source · 185 lines · MIT
SKCitationChip.swift
//
// SKCitationChip.swift
// Swift Kit UI
//
// Requires: SwiftKitUI.swift
//
// An inline source marker that opens the source.
//
// Citations are the difference between an assistant people check and one they
// either trust blindly or not at all. The constraint is that the marker has to
// sit *inside* a sentence without wrecking its rhythm — so it is sized to the
// caption ramp, given a fixed minimum width so `1` and `12` do not jitter the
// line, and raised slightly to sit like a superscript rather than a button
// dropped into the paragraph.
//
// Tapping opens a popover rather than pushing a screen. A citation is a
// glance, and a glance should not cost a navigation stack — on iPhone
// `presentationCompactAdaptation` keeps it a popover instead of letting it
// become a full sheet.
//
// Usage:
// SKCitationChip(index: 1, source: source)
// SKCitationChip(source: source, style: .domain)
//
import SwiftUI
public struct SKCitationSource: Identifiable, Sendable, Hashable {
public let id: String
public let title: String
public let domain: String
public let snippet: String?
public let url: URL?
public init(
id: String = UUID().uuidString,
title: String,
domain: String,
snippet: String? = nil,
url: URL? = nil
) {
self.id = id
self.title = title
self.domain = domain
self.snippet = snippet
self.url = url
}
}
public struct SKCitationChip: View {
public enum Style: Sendable { case numbered, domain }
private let index: Int?
private let source: SKCitationSource
private let style: Style
@State private var showing = false
public init(index: Int? = nil, source: SKCitationSource, style: Style = .numbered) {
self.index = index
self.source = source
self.style = style
}
public var body: some View {
Button {
showing = true
SK.Haptic.play(.light)
} label: {
label
}
.buttonStyle(.skPress(scale: 0.9, haptic: nil))
.popover(isPresented: $showing) {
card
.frame(idealWidth: 280)
.presentationCompactAdaptation(.popover)
}
.accessibilityLabel("Source: \(source.title), \(source.domain)")
}
@ViewBuilder
private var label: some View {
switch style {
case .numbered:
Text(index.map(String.init) ?? "\u{2022}")
.font(SK.Type_.caption)
.monospacedDigit()
.foregroundStyle(SK.Palette.accent)
// Fixed width so a two-digit citation does not shove the
// sentence around it.
.frame(minWidth: 17, minHeight: 17)
.background(SK.Palette.accentSoft, in: RoundedRectangle(cornerRadius: 5, style: .continuous))
.baselineOffset(3)
case .domain:
HStack(spacing: SK.Space.xs) {
Image(systemName: "link")
.font(SK.Icon.symbol(SK.Icon.xs, weight: .bold))
Text(source.domain)
.font(SK.Type_.caption)
.lineLimit(1)
}
.foregroundStyle(SK.Palette.accent)
.padding(.horizontal, SK.Space.sm)
.padding(.vertical, SK.Space.xxs + 1)
.background(SK.Palette.accentSoft, in: Capsule())
}
}
private var card: some View {
VStack(alignment: .leading, spacing: SK.Space.sm) {
HStack(spacing: SK.Space.xs) {
Image(systemName: "globe")
.font(SK.Icon.symbol(SK.Icon.xs))
Text(source.domain)
.font(SK.Type_.caption)
}
.foregroundStyle(SK.Palette.labelTertiary)
Text(source.title)
.font(SK.Type_.headline)
.foregroundStyle(SK.Palette.label)
.fixedSize(horizontal: false, vertical: true)
if let snippet = source.snippet {
Text(snippet)
.font(SK.Type_.caption)
.foregroundStyle(SK.Palette.labelSecondary)
.lineLimit(4)
.fixedSize(horizontal: false, vertical: true)
}
if let url = source.url {
Link(destination: url) {
HStack(spacing: SK.Space.xs) {
Text("Open")
Image(systemName: "arrow.up.right")
.font(SK.Icon.symbol(SK.Icon.xs, weight: .bold))
}
.font(SK.Type_.callout)
.foregroundStyle(SK.Palette.accent)
}
}
}
.padding(SK.Space.lg)
.frame(maxWidth: 300, alignment: .leading)
}
}
#Preview("SKCitationChip") {
let sources = [
SKCitationSource(
title: "SaaS Benchmarks 2026: Retention and Expansion",
domain: "openviewpartners.com",
snippet: "Median annual-plan mix rose to 58% among companies above $1M ARR, with churn concentrated in month-to-month cohorts.",
url: URL(string: "https://example.com")
),
SKCitationSource(
title: "Quarterly revenue teardown",
domain: "chartmogul.com",
snippet: "Mix shift toward annual billing typically depresses reported MRR growth for one quarter."
),
]
VStack(alignment: .leading, spacing: SK.Space.xl) {
Text("Annual plans now account for 61% of new subscriptions, up from 44% in Q2.")
.font(SK.Type_.body)
.foregroundStyle(SK.Palette.label)
.fixedSize(horizontal: false, vertical: true)
HStack(spacing: SK.Space.xs) {
Text("Cited:")
.font(SK.Type_.caption)
.foregroundStyle(SK.Palette.labelTertiary)
SKCitationChip(index: 1, source: sources[0])
SKCitationChip(index: 2, source: sources[1])
}
SKCitationChip(source: sources[0], style: .domain)
}
.padding(SK.Space.xl)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(SK.Palette.surface)
}
Questions
- Is this SwiftUI citation chip 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?
- SKCitationChip.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.