**Status**: Production Ready ✅
/plugin marketplace add secondsky/claude-skills/plugin install swift-settingskit@claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/basic-settings-template.swiftassets/custom-style-template.swiftassets/demo-app-template.swiftassets/modular-settings-template.swiftreferences/advanced-patterns.mdreferences/api-reference.mdreferences/performance-edge-cases.mdreferences/search-implementation.mdreferences/styling-guide.mdStatus: Production Ready ✅ Last Updated: 2025-11-23 Dependencies: None (standalone Swift package) Latest Version: SettingsKit 1.0.0+
Minimum Requirements:
Note: While @Observable was introduced in Swift 5.9, SettingsKit's Package.swift specifies Swift 6.0+ as the minimum toolchain version. All examples in this skill target Swift 6.0+.
Add the Swift package to your project via Xcode:
// File → Add Package Dependencies
// Enter: https://github.com/aeastr/SettingsKit.git
// Version: 1.0.0 or later
Or via Package.swift:
dependencies: [
.package(url: "https://github.com/aeastr/SettingsKit.git", from: "1.0.0")
]
Why this matters:
import SwiftUI
import SettingsKit
@Observable
class AppSettings {
var notificationsEnabled = true
var darkMode = false
var username = "Guest"
var fontSize: Double = 14.0
}
CRITICAL:
@Observable macro (not @Published or ObservableObject)struct MySettings: SettingsContainer {
@Environment(AppSettings.self) var appSettings
var settingsBody: some SettingsContent {
@Bindable var settings = appSettings
SettingsGroup("General", systemImage: "gear") {
SettingsItem("Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
SettingsItem("Dark Mode") {
Toggle("Enable", isOn: $settings.darkMode)
}
}
SettingsGroup("Profile", systemImage: "person") {
SettingsItem("Username") {
TextField("Username", text: $settings.username)
}
SettingsItem("Font Size") {
Slider(value: $settings.fontSize, in: 10...24)
Text("\(Int(settings.fontSize))pt")
}
}
}
}
CRITICAL:
@Bindable wrapper to create bindings from @Observable modelsettingsBody returns SettingsContent (not View)import SwiftUI
import SettingsKit
@main
struct MyApp: App {
@State private var settings = AppSettings()
var body: some Scene {
WindowGroup {
MySettings()
.environment(settings)
}
}
}
Result: Complete settings interface with:
Add SettingsKit via Swift Package Manager in Xcode:
https://github.com/aeastr/SettingsKit.gitKey Points:
Create an @Observable class to hold your settings state:
import SwiftUI
@Observable
class AppSettings {
// General settings
var notificationsEnabled = true
var soundEnabled = true
var hapticFeedback = true
// Appearance settings
var darkMode = false
var accentColor: Color = .blue
var fontSize: Double = 16.0
// User profile
var username = ""
var email = ""
var profileImageURL: URL?
}
Key Points:
@Observable macro (Swift 6.0+) for modern observationImplement SettingsContainer protocol to define your settings UI:
import SettingsKit
struct MySettings: SettingsContainer {
@Environment(AppSettings.self) var appSettings
var settingsBody: some SettingsContent {
@Bindable var settings = appSettings
// Navigation group (tappable row)
SettingsGroup("General", systemImage: "gear") {
SettingsItem("Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
SettingsItem("Sound Effects") {
Toggle("Enable", isOn: $settings.soundEnabled)
}
}
.settingsTags(["notifications", "sounds", "alerts"])
// Inline group (section header)
SettingsGroup("Quick Settings", .inline) {
SettingsItem("Dark Mode") {
Toggle("Enable", isOn: $settings.darkMode)
}
}
// Nested navigation
SettingsGroup("Profile", systemImage: "person") {
SettingsGroup("Account", systemImage: "person.circle") {
SettingsItem("Username") {
TextField("Username", text: $settings.username)
}
SettingsItem("Email") {
TextField("Email", text: $settings.email)
}
}
}
}
}
Key Points:
SettingsGroup creates navigation links (default) or section headers (.inline)SettingsItem wraps individual controls.settingsTags([...]) for enhanced search discoverabilityChoose how settings are displayed:
// Sidebar style (default) - Split view on iPad/Mac
MySettings(settings: settings)
.settingsStyle(.sidebar)
// Single column style - Clean list on all platforms
MySettings(settings: settings)
.settingsStyle(.single)
// Custom style - Full control over appearance
MySettings(settings: settings)
.settingsStyle(MyCustomStyle())
Key Points:
.sidebar: NavigationSplitView with selection-based navigation (default).single: Single NavigationStack with push navigationSettingsStyle protocol✅ Use @Observable for settings models - Required for SettingsKit's reactive system
✅ Wrap environment settings with @Bindable - Enables two-way binding in settingsBody
✅ Add searchable tags to important groups - Improves discoverability via .settingsTags([...])
✅ Keep settings models in SwiftUI environment - Use .environment(settings) on parent view
✅ Use SettingsItem for all interactive controls - Ensures proper search indexing
❌ Never use ObservableObject with @Published - SettingsKit requires modern @Observable ❌ Never create bindings without @Bindable wrapper - Will cause compilation errors ❌ Never put heavy computation in settingsBody - Computed on every render, keep lightweight ❌ Never forget to inject settings into environment - Causes runtime crashes ❌ Never use CustomSettingsGroup for simple controls - Bypasses search indexing unnecessarily
This skill prevents 5 documented issues:
Error: Compilation error when trying to bind to @Observable properties without @Bindable wrapper
Source: Swift concurrency migration guide, Observable macro documentation
Why It Happens: @Observable models require @Bindable wrapper to create bindings, unlike @Published properties
Prevention: Always use @Bindable var settings = appSettings in settingsBody before creating bindings
Error: Toggle switches, sliders, and text fields don't reflect model changes
Source: SettingsKit GitHub issues, SwiftUI observation system documentation
Why It Happens: Settings model not properly injected into SwiftUI environment, breaking observation
Prevention: Use .environment(settings) on parent view and @Environment(AppSettings.self) in SettingsContainer
Error: Selecting settings items doesn't navigate, or navigation stack becomes corrupted Source: SettingsKit architecture documentation, NavigationSplitView best practices Why It Happens: Using NavigationLink directly instead of SettingsGroup in sidebar style causes state conflicts Prevention: Always use SettingsGroup for navigation (never raw NavigationLink), let SettingsKit manage navigation state
Error: CustomSettingsGroup content is invisible to search functionality Source: SettingsKit README - "Custom groups are searchable by title/icon/tags but content renders without element indexing" Why It Happens: CustomSettingsGroup bypasses standard indexing for full UI control, only metadata is searchable Prevention: Use regular SettingsGroup/SettingsItem for searchable content, reserve CustomSettingsGroup for complex custom UI
Error: App crashes when opening settings on macOS with destination-based navigation issues Source: SettingsKit macOS-specific implementation notes Why It Happens: macOS uses destination-based NavigationLink (not selection-based) to prevent control update issues, but requires proper navigation state setup Prevention: Let SettingsKit handle navigation stack creation, don't wrap SettingsContainer in custom NavigationStack on macOS
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.iOS(.v17),
.macOS(.v14),
.watchOS(.v10),
.tvOS(.v17),
.visionOS(.v1)
],
products: [
.executable(name: "MyApp", targets: ["MyApp"])
],
dependencies: [
.package(url: "https://github.com/aeastr/SettingsKit.git", from: "1.0.0")
],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [
.product(name: "SettingsKit", package: "SettingsKit")
]
)
]
)
Why these settings:
Extract complex settings sections into separate SettingsContent types for better organization:
struct NotificationSettings: SettingsContent {
@Bindable var settings: AppSettings
var body: some SettingsContent {
SettingsGroup("Notifications", systemImage: "bell") {
SettingsItem("Enable Notifications") {
Toggle("Enable", isOn: $settings.notificationsEnabled)
}
if settings.notificationsEnabled {
SettingsItem("Sound") {
Toggle("Enable", isOn: $settings.soundEnabled)
}
SettingsItem("Badge") {
Toggle("Show badge", isOn: $settings.badgeEnabled)
}
}
}
.settingsTags(["notifications", "alerts", "sounds", "badges"])
}
}
// Use in main settings:
var settingsBody: some SettingsContent {
NotificationSettings(settings: settings)
AppearanceSettings(settings: settings)
PrivacySettings(settings: settings)
}
When to use: Complex settings hierarchies with 5+ groups, conditional content, or team collaboration on different sections
Use CustomSettingsGroup for advanced UIs while maintaining searchability:
CustomSettingsGroup("Developer Tools", systemImage: "hammer") {
VStack(spacing: 20) {
GroupBox("Debug Information") {
VStack(alignment: .leading, spacing: 8) {
Text("App Version: 1.0.0")
Text("Build: 42")
Text("Environment: Production")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
Button("Clear Cache") {
clearCache()
}
.buttonStyle(.borderedProminent)
Button("Export Logs", systemImage: "square.and.arrow.up") {
exportLogs()
}
}
.padding()
}
.settingsTags(["debug", "testing", "logs", "advanced"])
When to use: Complex custom UI that doesn't fit standard SettingsItem pattern, but still needs search discoverability via tags
Show/hide settings based on feature flags or user permissions:
SettingsGroup("Advanced", systemImage: "gearshape.2") {
SettingsItem("Enable Advanced Features") {
Toggle("Enable", isOn: $settings.showAdvanced)
}
if settings.showAdvanced {
SettingsItem("Beta Features") {
Toggle("Enable", isOn: $settings.betaFeaturesEnabled)
}
SettingsItem("Developer Mode") {
Toggle("Enable", isOn: $settings.developerMode)
}
}
if settings.developerMode {
SettingsGroup("Developer Options", systemImage: "wrench.and.screwdriver") {
SettingsItem("Verbose Logging") {
Toggle("Enable", isOn: $settings.verboseLogging)
}
}
}
}
When to use: Feature flags, user permission levels, progressive disclosure of complexity
SettingsItem supports all standard SwiftUI controls:
SettingsGroup("All Controls", systemImage: "slider.horizontal.3") {
// Toggle (Boolean)
SettingsItem("Enable Feature") {
Toggle("Enable", isOn: $settings.featureEnabled)
}
// Slider (Continuous value)
SettingsItem("Volume") {
VStack(alignment: .leading, spacing: 8) {
Slider(value: $settings.volume, in: 0...100)
Text("\(Int(settings.volume))%")
.font(.caption)
.foregroundStyle(.secondary)
}
}
// TextField (Text input)
SettingsItem("Username") {
TextField("Enter username", text: $settings.username)
.textFieldStyle(.roundedBorder)
}
// Picker (Selection)
SettingsItem("Theme") {
Picker("", selection: $settings.theme) {
Text("Light").tag(Theme.light)
Text("Dark").tag(Theme.dark)
Text("Auto").tag(Theme.auto)
}
.pickerStyle(.segmented)
}
// Stepper (Increment/Decrement)
SettingsItem("Font Size") {
Stepper("\(Int(settings.fontSize)) pt", value: $settings.fontSize, in: 10...24)
}
// Button (Action)
SettingsItem("Reset Settings") {
Button("Reset") {
resetSettings()
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
// ColorPicker (Color selection)
SettingsItem("Accent Color") {
ColorPicker("Choose color", selection: $settings.accentColor)
}
// DatePicker (Date/Time selection)
SettingsItem("Reminder Time") {
DatePicker("", selection: $settings.reminderTime, displayedComponents: .hourAndMinute)
}
}
When to use: Reference for available controls when building settings
Load additional reference files for detailed documentation on specific topics:
Load references/api-reference.md when implementing advanced SettingsContainer customization, custom SettingsContent types, or need detailed API documentation for all protocols and modifiers
Load references/styling-guide.md when customizing settings appearance, implementing custom SettingsStyle, or need platform-specific styling guidance
Load references/search-implementation.md when implementing custom search logic, debugging search behavior, or need to understand SettingsKit's search architecture
Load references/advanced-patterns.md when building complex nested hierarchies, implementing dynamic settings, working with persistence, or need production-ready architectural patterns
Load references/performance-edge-cases.md when building large settings hierarchies (100+ groups or 1000+ items), experiencing performance issues, or need stress testing guidance and optimization strategies
No scripts included - SettingsKit is a pure Swift package with no build scripts needed.
Detailed documentation files for advanced topics:
references/api-reference.md - Complete API documentation for all SettingsKit protocols, types, and modifiersreferences/styling-guide.md - Comprehensive guide to customizing settings appearance and platform-specific behaviorsreferences/search-implementation.md - Deep dive into search architecture, custom search implementation, and search scoringreferences/advanced-patterns.md - Production patterns for complex hierarchies, state management, persistence, and testingreferences/performance-edge-cases.md - Performance optimization, stress testing, and handling large hierarchiesWhen to load: See "When to Load References" section above for specific scenarios
Swift template files for quick setup:
assets/basic-settings-template.swift - Complete minimal settings implementationassets/custom-style-template.swift - Custom SettingsStyle implementation template (5 examples)assets/modular-settings-template.swift - Multi-file settings organization patternassets/demo-app-template.swift - Full demo app matching official SettingsKit demo (25+ properties, all control types, stress tests)Required:
Optional:
{
"dependencies": {
"SettingsKit": "1.0.0"
}
}
Platform Requirements:
This skill is based on the official SettingsKit repository and examples:
Solution: Import SettingsKit at top of file: import SettingsKit. Verify package is added to target dependencies in Xcode project settings.
Solution: Ensure settings model is in SwiftUI environment (.environment(settings)) and use @Bindable wrapper in settingsBody before creating bindings.
Solution: Verify using SettingsGroup (not raw NavigationLink), and not wrapping SettingsContainer in custom NavigationStack/NavigationSplitView.
Solution: Add .settingsTags([...]) to groups with relevant keywords. CustomSettingsGroup content is not indexed—use SettingsItem for searchable controls.
Use this checklist to verify your setup:
Questions? Issues?
references/api-reference.md for detailed API documentationreferences/advanced-patterns.md for complex scenarios