ersanq/skeletonui
A native SwiftUI skeleton loading library that makes your loading states look effortless.
Features
- ✅ One-line API —
.skeleton(isLoading: viewModel.isLoading) - ✅ Three shapes: rectangle, circle, capsule
- ✅ Smooth shimmer animation with customizable speed and colors
- ✅ Respects Reduce Motion accessibility setting
- ✅ Animates gracefully in and out
- ✅ Zero dependencies — pure SwiftUI
- ✅ iOS 16+, macOS 13+, tvOS 16+, watchOS 9+, visionOS 1+
Installation
Swift Package Manager (Recommended)
In Xcode: File → Add Package Dependencies and enter:
https://github.com/ErsanQ/SkeletonUIOr add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/ErsanQ/SkeletonUI", from: "1.0.0")
]Quick Start
import SkeletonUI
struct ProfileView: View {
@State private var isLoading = true
var body: some View {
HStack(spacing: 12) {
// Circle skeleton for avatar
Image("avatar")
.resizable()
.frame(width: 48, height: 48)
.clipShape(Circle())
.skeleton(isLoading: isLoading, shape: .circle)
VStack(alignment: .leading, spacing: 6) {
// Text skeletons
Text("John Appleseed")
.skeleton(isLoading: isLoading)
Text("iOS Developer")
.skeleton(isLoading: isLoading)
}
}
}
}Usage
Basic
Text("Article Title")
.skeleton(isLoading: isLoading)Shapes
// Profile picture
AsyncImage(url: url)
.skeleton(isLoading: isLoading, shape: .circle)
// Card
CardView()
.skeleton(isLoading: isLoading, shape: .rectangle(cornerRadius: 16))
// Badge
Text("Swift")
.skeleton(isLoading: isLoading, shape: .capsule)Custom Colors
let brandedConfig = SkeletonConfiguration(
baseColor: .purple.opacity(0.12),
highlightColor: .purple.opacity(0.25),
animationDuration: 1.2
)
Text("Branded content")
.skeleton(isLoading: isLoading, config: brandedConfig)Accessibility
Automatically disables animation when the user enables "Reduce Motion":
Text("Accessible content")
.skeletonAccessible(isLoading: isLoading)Building a Full Screen Skeleton
struct ArticleListView: View {
@StateObject private var viewModel = ArticleViewModel()
var body: some View {
List {
ForEach(viewModel.isLoading ? placeholders : viewModel.articles) { item in
ArticleRow(article: item)
.skeleton(isLoading: viewModel.isLoading)
}
}
.task { await viewModel.load() }
}
private var placeholders: [Article] {
(0..<5).map { Article.placeholder(id: $0) }
}
}API Reference
View.skeleton(isLoading:shape:config:)
func skeleton(
isLoading: Bool,
shape: SkeletonShape = .rectangle(cornerRadius: 8),
config: SkeletonConfiguration = .default
) -> some ViewView.skeletonAccessible(isLoading:shape:)
func skeletonAccessible(
isLoading: Bool,
shape: SkeletonShape = .rectangle(cornerRadius: 8)
) -> some ViewSkeletonShape
| Case | Description | |------|-------------| | .rectangle(cornerRadius: CGFloat = 8) | Rounded rectangle | | .circle | Perfect circle | | .capsule | Pill shape |
SkeletonConfiguration
| Property | Type | Default | Description | |----------|------|---------|-------------| | baseColor | Color | systemGray5 | Base fill color | | highlightColor | Color | systemGray4 | Shimmer highlight | | animationDuration | Double | 1.5 | Seconds per sweep | | isAnimated | Bool | true | Enable/disable animation |
Presets: .default, .reducedMotion
Requirements
- iOS 16.0+ / macOS 13.0+ / tvOS 16.0+ / watchOS 9.0+ / visionOS 1.0+
- Swift 5.9+
- Xcode 15.0+
License
SkeletonUI is available under the MIT license. See the LICENSE file for more info.
Package Metadata
Repository: ersanq/skeletonui
Default branch: main
README: README.md