Jko-claude-plugins swiftui-expert
This skill should be used when the user is building, reviewing, or debugging SwiftUI views and apps. Detects iOS and Swift version from the project. Covers creating views, state management with @Observable, NavigationStack routing, animations, accessibility, performance optimization, Liquid Glass adoption, design systems, and clean code architecture. Use when the user asks things like "create a SwiftUI list view", "my @State isn't updating", "add navigation to my app", "make this accessible", "optimize SwiftUI performance", "add Liquid Glass to my toolbar", "fix my Swift concurrency warning", "set up SwiftData models", "critique my SwiftUI code", "fix my SwiftUI layout", "create a custom ViewModifier", or "add Dark Mode support".
git clone https://github.com/johnkozaris/jko-claude-plugins
T=$(mktemp -d) && git clone --depth=1 https://github.com/johnkozaris/jko-claude-plugins "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/swiftui/skills/swiftui-expert" ~/.claude/skills/johnkozaris-jko-claude-plugins-swiftui-expert && rm -rf "$T"
plugins/swiftui/skills/swiftui-expert/SKILL.mdSwiftUI Expert
Provide expert guidance on SwiftUI development. Detect the project's deployment target and Swift version from
Package.swift, .xcodeproj, or project settings and adapt guidance accordingly. Apply modern API usage, clean code principles, design craft, accessibility, and performance best practices. Do not invent APIs — if unsure, say so.
Core Principles
- Target the latest stable iOS and Swift unless the project specifies otherwise. Check the deployment target before suggesting version-gated APIs.
- Prefer SwiftUI-native solutions. Avoid UIKit unless explicitly needed or for gaps SwiftUI cannot fill.
- Do not introduce third-party frameworks without asking first.
- Each type (struct, class, enum) belongs in its own file. Flag files with multiple type definitions.
- Use a feature-based folder structure, not layer-based (no "ViewModels/" folder).
- Code must adhere to Apple's Human Interface Guidelines.
Modern API — Always Use
Replace deprecated API immediately. See
references/modern-api.md for the complete table.
Key replacements:
notforegroundStyle()foregroundColor()
notclipShape(.rect(cornerRadius:))cornerRadius()
/NavigationStack
notNavigationSplitViewNavigationView
not@Observable
/ObservableObject
/@Published
/@StateObject@ObservedObject
notnavigationDestination(for:)NavigationLink(destination:)
API notTabtabItem()
not UIKit hapticssensoryFeedback()
not#PreviewPreviewProvider
orcontainerRelativeFrame()
notvisualEffect()
(when possible)GeometryReader
notTask.sleep(for:)Task.sleep(nanoseconds:)
macro for custom environment/focus/transaction keys@Entry
State Management
must be@State
. It is owned by the view that creates it.private
classes must be@Observable
(unless the project uses default MainActor isolation).@MainActor- Use
for ownership of@State
objects,@Observable
for bindings to them,@Bindable
for passing them.@Environment - Never use
inside@AppStorage
classes — it will not trigger view updates.@Observable - Never create
in body — useBinding(get:set:)
+@State
.onChange() - Nested
works fine. Nested@Observable
does not propagate changes.ObservableObject - For
classes, mark properties you don't want tracked with@Observable
.@ObservationIgnored
See
references/state-data.md for property wrapper decision flowchart and SwiftData rules.
View Composition & Clean Code
- Strongly prefer extracting subviews into separate
structs over computed properties or methods returningView
. Computed properties get no re-evaluation isolation fromsome View
— separate structs allow SwiftUI to skip unchanged subviews entirely.@Observable - Keep
short and computation-free. No sorting, filtering, or formatter creation inbody
.body - Extract button actions into methods. No inline business logic in
,task()
, oronAppear()
.body - Apply DRY: repeated styling →
. Repeated layout → extractedViewModifier
. Repeated logic → model/service method.View - Apply Single Responsibility: each view does one thing. Each model owns one domain. Each file contains one type.
- Apply Open/Closed: extend behavior through protocols and extensions, not by modifying existing types. Use
+ViewModifier
extension for reusable styling.View - Prefer
/overlay
for decoration;background
for peer composition.ZStack - Container views: use
(not stored closures).@ViewBuilder let content: Content
See
references/view-composition.md for patterns, ordering conventions, and anti-patterns.
Design Craft
- Establish a clear visual direction — commit to an aesthetic, don't mix styles.
- Avoid generic AI aesthetics: no cards for everything, no gratuitous gradients/materials/shadows, no bouncy animations everywhere, no web-style CTA buttons. Use native iOS components. Apply the squint test — hierarchy should be visible even blurred.
- Define design tokens (colors, typography, spacing, corner radii, animation timings) in a shared constants enum. One source of truth.
- Follow the 60-30-10 color rule: 60% dominant neutral, 30% secondary, 10% accent.
- Use semantic color names for role, not RGB. Support dark mode with system colors or asset catalog.
- Establish clear typographic hierarchy: 3-4 levels max. Use weight contrast more than size contrast.
- Use Dynamic Type fonts exclusively. Never hardcode
..font(.system(size:)) - Minimum 44x44pt tap targets. Handle all interaction states (default, pressed, disabled, loading, error, success).
- Write clear UX copy: verb-based button labels, actionable error messages, helpful empty states.
See
references/design-craft.md for visual hierarchy, color harmony, typography pairing, interaction states, spacing tokens, UX writing, and HIG alignment.
Animation & Motion
- Use
(explicit) for control. UsewithAnimation { }
for implicit — always with a.animation(_:value:)
parameter.value: - Prefer GPU-friendly transforms (
,offset
,scale
) over layout changes (rotation
).frame - Use
macro (iOS 26+) not manual@Animatable
.animatableData - Chain animations via
completion closures, not delays.withAnimation
withmatchedGeometryEffect
for hero transitions.@Namespace- Respect
— replace motion with opacity.accessibilityReduceMotion - Spring animations for natural feel.
for playful,.bouncy
for subtle..smooth
See
references/animation.md for phase/keyframe animators, transitions, and Liquid Glass morphing.
Accessibility
- VoiceOver: every interactive element needs a text label.
not icon-only.Button("Add", systemImage: "plus", action:) - Use
orImage(decorative:)
for decorative images.accessibilityHidden() - Never use
whenonTapGesture()
works. If you must, addButton
..accessibilityAddTraits(.isButton) - Respect
— don't rely on color alone.accessibilityDifferentiateWithoutColor - Use
for complex/changing button labels.accessibilityInputLabels() - Test with VoiceOver and Accessibility Inspector.
See
references/accessibility.md for Dynamic Type, element grouping, custom controls, and charts accessibility.
Performance
- Ternary expressions over
view branching (avoidsif/else
)._ConditionalContent - No
. UseAnyView
,@ViewBuilder
, or generics.Group - Fine-grained
models — avoid broad dependencies on arrays.@Observable - Use
/LazyVStack
for large data sets.LazyHStack
overtask()
for async (auto-cancellation).onAppear()- Keep view initializers trivial. Defer work to
.task() - Debug with
and random background colors.Self._printChanges()
See
references/performance.md for the full code smell catalog and remediation patterns.
Concurrency
- Always
/async
over closures. Never use GCD (await
).DispatchQueue
on@MainActor
classes (if not using default MainActor isolation).@Observable- Use
modifier for async work in views..task - Prefer
overTask
(inherits actor context).Task.detached - Flag unprotected mutable shared state.
- Assume strict concurrency. Flag
violations.@Sendable
See
references/concurrency.md for actor patterns, Sendable, and Swift 6 migration.
Navigation & Presentation
withNavigationStack
. Never mix withnavigationDestination(for:)
.NavigationLink(destination:)
oversheet(item:)
for optional data.sheet(isPresented:)- Attach
to its trigger (for Liquid Glass animations).confirmationDialog() - Single "OK" alert buttons can be omitted.
binds to an enum, not Int/String.TabView(selection:)
See
references/navigation.md for split views, inspector patterns, and deep links.
Liquid Glass (iOS 26+)
- Recompile with Xcode 26 for automatic adoption on NavigationBar, TabBar, Toolbar.
- Apply
after layout and visual modifiers..glassEffect() - Use
when multiple glass elements coexist.GlassEffectContainer
only on tappable/focusable elements..interactive()
/.buttonStyle(.glass)
for actions..buttonStyle(.glassProminent)- Gate with
and provide#available(iOS 26, *)
fallback..ultraThinMaterial
See
references/liquid-glass.md for morphing transitions, design system notes, and scroll edge effects.