
Focus Engine
- 1.6k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
focus-engine is an agent skill for implement swiftui and uikit keyboard, directional, and scene-level focus behavior.
About
The focus-engine skill is designed for implement SwiftUI and UIKit keyboard, directional, and scene-level focus behavior. Focus Engine Focus behavior for SwiftUI and UIKit apps targeting iOS 26+, iPadOS, macOS, tvOS, and visionOS connected-input paths. Covers keyboard focus, directional focus, scene-focused values, focus restoration, and UIKit focus guides. Invoke when the user manages @FocusState, UIFocusGuide, focus sections, or tvOS/visionOS focus routing.
- Use this skill for keyboard, remote, game-controller, and scene focus behavior.
- For visionOS, describe gaze, direct touch, and pointer targeting as hover/input affordances, not focus.
- SwiftUI FocusState.
- Default Focus.
- Focused Values and Scene Values.
Focus Engine by the numbers
- 1,581 all-time installs (skills.sh)
- +111 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #154 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
focus-engine capabilities & compatibility
- Capabilities
- use this skill for keyboard, remote, game contro · for visionos, describe gaze, direct touch, and p · swiftui focusstate · default focus
- Use cases
- frontend
What focus-engine says it does
Implements keyboard, directional, and scene-level focus behavior across SwiftUI and UIKit. Use when managing @FocusState, defaultFocus, focused values, focusable interactions, focu
Implements keyboard, directional, and scene-level focus behavior across SwiftUI and UIKit. Use when managing @FocusState, defaultFocus, focused values, focusabl
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill focus-engineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
How do I implement swiftui and uikit keyboard, directional, and scene-level focus behavior?
Implement SwiftUI and UIKit keyboard, directional, and scene-level focus behavior.
Who is it for?
iOS developers managing @FocusState, focus guides, tvOS navigation, and focus restoration.
Skip if: Skip for VoiceOver-only accessibility work covered by ios-accessibility skill.
When should I use this skill?
User manages @FocusState, UIFocusGuide, focus sections, or tvOS/visionOS focus routing.
What you get
Completed focus-engine workflow with documented commands, files, and expected deliverables.
- focus-aware view implementations
- platform-specific focus handlers
By the numbers
- Targets iOS 26+ alongside iPadOS, macOS, tvOS, watchOS, and visionOS
Files
Focus Engine
Focus behavior for SwiftUI and UIKit apps targeting iOS 26+, iPadOS, macOS, tvOS, and visionOS connected-input paths. Covers keyboard focus, directional focus, scene-focused values, focus restoration, and UIKit focus guides. focusSection() guidance in this skill applies to macOS and tvOS. visionOS gaze-driven hover is an input affordance, not focus. Accessibility-specific focus for VoiceOver and Switch Control lives in the ios-accessibility skill.
When a request mixes focus with accessibility or spatial input, keep the boundary explicit:
- Use this skill for keyboard, remote, game-controller, and scene focus behavior.
- For visionOS, describe gaze, direct touch, and pointer targeting as hover/input affordances, not focus.
- For VoiceOver, Switch Control, Voice Control, or accessibility element ordering, give only a brief handoff to
ios-accessibility.
Contents
- SwiftUI FocusState
- Default Focus
- Focused Values and Scene Values
- Focusable Interactions
- Focus Sections
- Focus Restoration
- UIKit Focus Guides
- Common Mistakes
- Review Checklist
- References
SwiftUI FocusState
Use @FocusState to read and write focus placement inside a scene. Use Bool for a single target or an optional Hashable enum for multiple targets.
struct LoginView: View {
enum Field: Hashable { case email, password }
@State private var email = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
}
.onAppear { focusedField = .email }
.onSubmit {
switch focusedField {
case .email: focusedField = .password
case .password, nil: submit()
}
}
}
}Keep focus state local to the view that owns the focusable controls.
Default Focus
Use .defaultFocus to set the preferred initial focus region or control when a view appears or when focus is reassigned automatically.
struct SidebarView: View {
enum Target: Hashable { case library, settings }
@FocusState private var focusedTarget: Target?
var body: some View {
VStack {
Button("Library") { }
.focused($focusedTarget, equals: .library)
Button("Settings") { }
.focused($focusedTarget, equals: .settings)
}
.defaultFocus($focusedTarget, .library)
}
}Prefer one clear default destination per screen or focus region.
Focused Values and Scene Values
Use focused values to expose state from the currently focused view. Use scene-focused values when commands or scene-wide UI should keep access to the value even after focus moves within that scene.
struct SelectedRecipeKey: FocusedValueKey {
typealias Value = Binding<Recipe>
}
extension FocusedValues {
var selectedRecipe: Binding<Recipe>? {
get { self[SelectedRecipeKey.self] }
set { self[SelectedRecipeKey.self] = newValue }
}
}
struct RecipeDetailView: View {
@Binding var recipe: Recipe
var body: some View {
Text(recipe.title)
.focusedSceneValue(\.selectedRecipe, $recipe)
}
}Use this pattern for menus, commands, and toolbars that need to act on the focused scene's current content.
Focusable Interactions
Use .focusable(_:interactions:) on custom SwiftUI views that should participate in keyboard or directional focus.
struct SelectableCard: View {
let title: String
let action: () -> Void
@FocusState private var isFocused: Bool
var body: some View {
Button(action: action) {
RoundedRectangle(cornerRadius: 12)
.fill(isFocused ? Color.accentColor.opacity(0.15) : .clear)
.overlay { Text(title) }
}
.buttonStyle(.plain)
.focusable(interactions: .activate)
.focused($isFocused)
}
}Prefer semantic Button, Toggle, TextField, and other system controls before making arbitrary gesture-driven views focusable. Use .focusable(interactions: .activate) for custom button-like controls only when a semantic control cannot express the UI. Reserve broader interactions for views that genuinely need editing or multiple focus-driven behaviors.
Focus Sections
Use focusSection() on macOS 13+ and tvOS 15+ to guide directional movement across groups of focusable descendants in uneven layouts.
struct TVLibraryView: View {
var body: some View {
HStack {
VStack {
Button("Recent") { }
Button("Favorites") { }
Button("Downloaded") { }
}
.focusSection()
VStack {
Button("Featured") { }
Button("Top Picks") { }
Button("Continue Watching") { }
}
.focusSection()
}
}
}Use focus sections on macOS and tvOS when default left/right or up/down movement skips the intended group.
Focus Restoration
After dismissing a sheet, popover, or transient overlay, return focus to a stable trigger or logical next target.
struct FiltersView: View {
@State private var showSheet = false
@FocusState private var isFilterButtonFocused: Bool
var body: some View {
Button("Filters") { showSheet = true }
.focused($isFilterButtonFocused)
.sheet(isPresented: $showSheet) {
FilterEditor()
.onDisappear {
Task { @MainActor in
isFilterButtonFocused = true
}
}
}
}
}Restore focus intentionally whenever presentation changes would otherwise leave users disoriented.
UIKit Focus Guides
Use UIFocusGuide when UIKit or tvOS layouts need custom routing across empty space or awkward geometry.
final class DashboardViewController: UIViewController {
private let focusGuide = UIFocusGuide()
@IBOutlet private weak var leadingButton: UIButton!
@IBOutlet private weak var trailingButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.addLayoutGuide(focusGuide)
focusGuide.preferredFocusEnvironments = [trailingButton]
NSLayoutConstraint.activate([
focusGuide.leadingAnchor.constraint(equalTo: leadingButton.trailingAnchor),
focusGuide.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor),
focusGuide.topAnchor.constraint(equalTo: leadingButton.topAnchor),
focusGuide.bottomAnchor.constraint(equalTo: leadingButton.bottomAnchor)
])
}
}UIFocusGuide is invisible and not a view. Use it to redirect focus without adding decorative UI.
Common Mistakes
1. Mixing accessibility focus and keyboard or directional focus in the same mental model. 2. Storing @FocusState in shared models instead of the owning view. 3. Setting multiple competing default focus targets on one screen. 4. Using .focusable() on decorative views. 5. Forgetting focus restoration after sheets, popovers, or custom overlays. 6. Reaching for UIFocusGuide before trying focusSection() on macOS or tvOS, or better layout grouping in SwiftUI. 7. Using gesture handlers for primary actions on custom focusable controls instead of a semantic Button when possible. 8. Treating visionOS gaze hover as focus; reserve focus guidance for connected input such as keyboards and game controllers.
Review Checklist
- [ ]
@FocusStateis local to the view that owns the controls - [ ] Initial focus target is explicit when the screen needs one
- [ ] Focus movement between fields or groups is deterministic
- [ ]
focusedSceneValueor related focused-value APIs are used when commands need current scene state - [ ] Custom controls opt into focus only when they are truly interactive
- [ ]
focusSection()is used for uneven directional layouts on macOS or tvOS before dropping to UIKit - [ ] Focus returns to a stable element after temporary presentations dismiss
- [ ]
UIFocusGuidegeometry and preferred destinations match the intended route - [ ] visionOS guidance distinguishes connected-device focus from gaze-driven hover or RealityKit input targets
- [ ] Accessibility focus concerns are handled in
ios-accessibility, not mixed into keyboard-directional focus logic
References
- Detailed patterns: references/focus-patterns.md
- Multi-platform focus (tvOS, watchOS, visionOS, macOS): references/multi-platform-focus.md
- Focus debugging and anti-patterns: references/focus-debugging.md
{
"skill_name": "focus-engine",
"evals": [
{
"id": 0,
"prompt": "Review this SwiftUI focus plan for an iPadOS form and command menu: every TextField uses its own Bool @FocusState, two fields reuse the same enum case, the screen sets several defaultFocus modifiers, and toolbar commands read a selected document from global app state. Give concise corrected guidance with Swift where useful.",
"expected_output": "A correction-focused answer that uses one optional Hashable focus state for multiple fields, avoids ambiguous focus bindings, picks one default target or scoped default, and uses focused scene values for command/menu state.",
"files": [],
"assertions": [
"Recommends one optional Hashable enum @FocusState for multiple focus targets rather than separate competing Bool focus states.",
"Explains that reusing the same focus enum value across multiple fields is ambiguous and should be avoided.",
"Recommends one clear default focus target or a scoped default rather than several competing defaultFocus modifiers.",
"Uses focusedSceneValue or focused values for scene/menu/toolbar actions that depend on the current focused content.",
"Keeps accessibility focus concerns out of the answer except as a handoff to ios-accessibility if needed."
]
},
{
"id": 1,
"prompt": "A tvOS SwiftUI media screen has a left sidebar, an uneven poster grid, and a row of custom card views. The team wants to handle Siri Remote swipes manually, make only the currently visible cards tappable by gestures, and add UIFocusGuide everywhere. Review the focus plan and suggest the right routing/debugging approach.",
"expected_output": "A tvOS-focused review that relies on the focus engine, makes actions reachable via focus plus select, uses semantic controls and focusSection for uneven SwiftUI regions before UIKit guides, and reserves UIFocusGuide/UIFocusDebugger for custom UIKit routing/debugging.",
"files": [],
"assertions": [
"States that tvOS actions must be reachable through focus movement and select or activation, not touch-only gestures.",
"Explains that the focus engine controls directional movement and should not be replaced with manual swipe routing for normal UI navigation.",
"Recommends semantic Button controls or explicit focusable custom controls for interactive cards.",
"Recommends focusSection() for uneven SwiftUI sidebar/grid regions before reaching for UIKit UIFocusGuide.",
"Uses UIFocusGuide only for UIKit or custom geometry gaps and mentions UIFocusDebugger as an LLDB debugging aid."
]
},
{
"id": 2,
"prompt": "I'm making a visionOS RealityKit scene and want gaze to move SwiftUI focus between floating controls, while InputTargetComponent and HoverEffectComponent make 3D entities part of the focus system. What should I change, and where should accessibility-specific focus guidance go?",
"expected_output": "A boundary-aware visionOS answer that separates connected-device focus from gaze hover, frames RealityKit input and hover components as interaction affordances rather than focus-system APIs, and routes VoiceOver/Switch Control details to ios-accessibility.",
"files": [],
"assertions": [
"States that visionOS gaze hover is not the focus system; connected devices such as keyboards or game controllers use focus navigation.",
"Frames InputTargetComponent plus CollisionComponent as enabling RealityKit entities to receive system input.",
"Frames HoverEffectComponent as hover feedback for gaze, direct touch, or pointer hover, not focus.",
"Does not claim that gaze should programmatically move SwiftUI @FocusState between controls.",
"Routes VoiceOver, Switch Control, or accessibility-specific focus details to the ios-accessibility skill."
]
}
]
}
Focus Debugging
Runtime tools for diagnosing focus issues in UIKit and SwiftUI apps.
Docs: UIFocusDebugger
UIFocusDebugger (LLDB)
UIFocusDebugger is a runtime-only class for use in the LLDB console during a debugging session. Do not call these methods from app code.
Commands
// Show current focus state
po UIFocusDebugger.status()
// Check why a specific view can't receive focus
po UIFocusDebugger.checkFocusability(for: myButton)
// Show focus group hierarchy
po UIFocusDebugger.focusGroups(for: myViewController)
// Show preferred focus chain
po UIFocusDebugger.preferredFocusEnvironments(for: myViewController)
// Simulate a focus update from a given environment
po UIFocusDebugger.simulateFocusUpdateRequest(from: myViewController)Common Diagnostic Patterns
"Why won't this view focus?"
po UIFocusDebugger.checkFocusability(for: myView)Common causes returned:
- View is hidden or has zero alpha
- View is not in the view hierarchy
canBecomeFocusedreturnsfalse- A parent's
shouldUpdateFocus(in:)returnedfalse - The view is covered by another view
"Where does focus go next?"
po UIFocusDebugger.simulateFocusUpdateRequest(from: currentView)Shows the focus engine's evaluation of the next destination based on geometry.
SwiftUI Focus Debugging
SwiftUI does not expose UIFocusDebugger directly. Strategies:
1. Add `.onChange(of: focusedField)` to log focus transitions:
.onChange(of: focusedField) { old, new in
print("Focus moved: \(String(describing: old)) → \(String(describing: new))")
}2. Use Accessibility Inspector (Xcode → Open Developer Tool) to inspect focus order and accessibility element hierarchy.
3. Set breakpoints in `didUpdateFocus(in:with:)` for UIKit-hosted views within SwiftUI.
Focus Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
Programmatically setting focus in viewDidLoad | Focus engine hasn't completed initial update | Use viewDidAppear or DispatchQueue.main.async |
Calling setNeedsFocusUpdate() without updateFocusIfNeeded() | Focus update is deferred indefinitely | Pair both calls: setNeedsFocusUpdate(); updateFocusIfNeeded() |
Overriding preferredFocusEnvironments with stale references | Focus targets a deallocated or off-screen view | Return currently valid, on-screen environments |
Using isHidden = true to disable focus on a view | Removes the view from layout entirely | Use canBecomeFocused override or focusable(false) |
Animating focus changes without UIFocusAnimationCoordinator | Animation doesn't sync with system focus animation | Use coordinator.addCoordinatedFocusingAnimations |
Forgetting collisionComponent on RealityKit entities | Entity can't receive gaze/direct-touch input or hover feedback in visionOS | Add CollisionComponent alongside InputTargetComponent |
| Not testing with Full Keyboard Access on macOS | Tab focus skips custom controls | Enable Keyboard Navigation in System Settings and test |
| Relying on touch-based interactions on tvOS | No touch input available | Make all actions accessible via focus + select |
Focus Patterns Reference
Contents
- FocusState patterns
- Default focus
- Focused values and scene values
- Focusable custom views
- Focus sections for directional movement (macOS/tvOS)
- Focus restoration after presentations
- UIKit focus guides
- Common mistakes checklist
FocusState patterns
struct CheckoutForm: View {
enum Field: Hashable { case address, city, postalCode }
@State private var address = ""
@State private var city = ""
@State private var postalCode = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Address", text: $address)
.focused($focusedField, equals: .address)
.onSubmit { focusedField = .city }
TextField("City", text: $city)
.focused($focusedField, equals: .city)
.onSubmit { focusedField = .postalCode }
TextField("Postal Code", text: $postalCode)
.focused($focusedField, equals: .postalCode)
}
.onAppear { focusedField = .address }
}
}Use Bool focus state only for one-off cases. Prefer a Hashable enum when more than one control can be focused.
Default focus
struct CommandPaletteView: View {
enum Target: Hashable { case search }
@FocusState private var target: Target?
var body: some View {
VStack {
TextField("Search commands", text: $query)
.focused($target, equals: .search)
}
.defaultFocus($target, .search)
}
}Choose one unambiguous default target. Competing defaults make focus feel unstable.
Focused values and scene values
struct SelectedDocumentKey: FocusedValueKey {
typealias Value = Binding<Document>
}
extension FocusedValues {
var selectedDocument: Binding<Document>? {
get { self[SelectedDocumentKey.self] }
set { self[SelectedDocumentKey.self] = newValue }
}
}
struct DocumentEditor: View {
@Binding var document: Document
var body: some View {
TextEditor(text: $document.body)
.focusedSceneValue(\.selectedDocument, $document)
}
}Use focused scene values when command menus, toolbar actions, or scene-level controls need access to the current focused content.
Focusable custom views
struct TVCardButton: View {
let title: String
let action: () -> Void
@FocusState private var isFocused: Bool
var body: some View {
Button(action: action) {
RoundedRectangle(cornerRadius: 16)
.fill(isFocused ? Color.accentColor.opacity(0.2) : Color.secondary.opacity(0.12))
.overlay { Text(title) }
}
.buttonStyle(.plain)
.focusable(interactions: .activate)
.focused($isFocused)
.scaleEffect(isFocused ? 1.04 : 1.0)
.animation(.snappy, value: isFocused)
}
}Prefer semantic controls like Button first. Add .focusable(interactions:) only when the custom control needs explicit focus participation. Do not make arbitrary gesture-only views the primary action target on tvOS or keyboard-driven interfaces; expose the action through a semantic control or a custom control with explicit focus and activation behavior.
Focus sections for directional movement
focusSection() is available on macOS 13+ and tvOS 15+.
struct LibraryView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Button("Recent") { }
Button("Favorites") { }
Button("Downloaded") { }
}
.focusSection()
LazyVGrid(columns: columns) {
ForEach(items) { item in
Button(item.title) { open(item) }
}
}
.focusSection()
}
}
}Use focusSection() on macOS or tvOS when the user should move through one group before jumping into another.
Focus restoration after presentations
struct SearchFiltersView: View {
@State private var isPresentingFilters = false
@FocusState private var isFiltersButtonFocused: Bool
var body: some View {
Button("Filters") {
isPresentingFilters = true
}
.focused($isFiltersButtonFocused)
.sheet(isPresented: $isPresentingFilters) {
FiltersSheet()
.onDisappear {
Task { @MainActor in
isFiltersButtonFocused = true
}
}
}
}
}Restore focus to the trigger or to the next logical destination after dismissing temporary UI.
UIKit focus guides
final class PlayerViewController: UIViewController {
private let skipGuide = UIFocusGuide()
@IBOutlet private weak var playButton: UIButton!
@IBOutlet private weak var nextEpisodeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.addLayoutGuide(skipGuide)
skipGuide.preferredFocusEnvironments = [nextEpisodeButton]
NSLayoutConstraint.activate([
skipGuide.leadingAnchor.constraint(equalTo: playButton.trailingAnchor),
skipGuide.trailingAnchor.constraint(equalTo: nextEpisodeButton.leadingAnchor),
skipGuide.topAnchor.constraint(equalTo: playButton.topAnchor),
skipGuide.bottomAnchor.constraint(equalTo: playButton.bottomAnchor)
])
}
}A focus guide is an invisible layout guide. Constrain it like any other layout guide, then set preferredFocusEnvironments to the destination views.
Common mistakes checklist
- Using shared model state to drive
@FocusState - Forgetting to restore focus after dismissing transient UI
- Making decorative containers focusable
- Adding multiple defaults to the same focus region
- Using UIKit focus guides for layouts SwiftUI can solve with
focusSection() - Publishing scene-focused values that should only be view-local
Multi-Platform Focus Patterns
Platform-specific focus behaviors beyond the common SwiftUI and UIKit patterns covered in the main SKILL.md.
Contents
tvOS Focus
tvOS uses a geometric focus model: the focus engine evaluates the spatial positions of focusable items and moves focus in the direction the user swipes on the Siri Remote.
Key Differences from iOS
- All actions must be reachable through focus movement and select/activate;
there is no touch path to fall back on.
- The focus engine moves focus automatically based on geometry; you cannot
programmatically set focus to an arbitrary item without the engine's consent.
UIFocusEnvironment.preferredFocusEnvironmentsdetermines the preferred
destination when focus enters a container.
UIFocusUpdateContextprovidespreviouslyFocusedItem,nextFocusedItem,
focusHeading, and animationCoordinator.
UICollectionView Focus on tvOS
override func collectionView(
_ collectionView: UICollectionView,
canFocusItemAt indexPath: IndexPath
) -> Bool {
// Prevent focus on disabled cells
let item = dataSource.itemIdentifier(for: indexPath)
return item?.isEnabled ?? false
}
override func collectionView(
_ collectionView: UICollectionView,
didUpdateFocusIn context: UIFocusUpdateContext,
with coordinator: UIFocusAnimationCoordinator
) {
coordinator.addCoordinatedFocusingAnimations { _ in
context.nextFocusedView?.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
} completion: {}
coordinator.addCoordinatedUnfocusingAnimations { _ in
context.previouslyFocusedView?.transform = .identity
} completion: {}
}focusSection() on tvOS
focusSection() (tvOS 15+) groups focusable children in SwiftUI so the focus engine treats them as a navigable region:
HStack {
VStack {
ForEach(sidebarItems) { item in
Button(item.title) { select(item) }
}
}
.focusSection()
LazyVGrid(columns: columns) {
ForEach(gridItems) { item in
CardView(item: item)
}
}
.focusSection()
}Without focusSection(), swiping right from the sidebar might land on a grid item at the wrong vertical position.
watchOS Focus
watchOS uses the Digital Crown as its primary navigation input alongside touch. SwiftUI provides digitalCrownRotation(_:) to track crown input.
Docs: digitalCrownRotation)
struct CrownScrollView: View {
@State private var offset: Double = 0
var body: some View {
ScrollView {
VStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
.digitalCrownRotation($offset, from: 0, through: 100)
}
}Focus on watchOS is simpler than tvOS — most views are linearly scrollable and focus is implicit via the crown/scroll position.
visionOS Focus and Hover
visionOS supports the focus system for connected input devices such as keyboards and game controllers. When a person looks at, directly touches, or points at a virtual object, the system uses hover feedback, not focus, to show the interaction target. Keep these paths separate: focus is for keyboard/game-controller navigation, while gaze, direct touch, hand input, and pointer input use hover effects and gestures.
Hover Effects
All interactive elements get automatic hover effects in visionOS. Customize with .hoverEffect(_:):
Button("Action") { }
.hoverEffect(.highlight) // Default for buttons
.hoverEffect(.lift) // Raises the elementRealityKit Hover and Input Targets
In RealityKit scenes, use InputTargetComponent, HoverEffectComponent, and a CollisionComponent to make entities receive system input and show hover feedback:
let entity = ModelEntity(mesh: .generateBox(size: 0.1))
entity.components.set(InputTargetComponent())
entity.components.set(HoverEffectComponent())
entity.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.1, 0.1])]))HoverEffectComponent is visual feedback for gaze, direct touch, or pointer hover. It is not a callback mechanism, does not move SwiftUI @FocusState, and does not make an entity part of the focus system.
Accessibility in visionOS
Do not expand this skill into VoiceOver, Switch Control, Voice Control, or accessibility focus ordering. Mention that those details belong in the ios-accessibility skill, then keep this reference focused on keyboard, game-controller, hover, and RealityKit input-target behavior.
macOS Focus
Key View Loop
UIKit-based macOS Catalyst apps and AppKit apps use the key view loop to determine Tab order. NSView.nextKeyView chains views together.
SwiftUI on macOS uses @FocusState identically to iOS, with Tab moving focus between fields by default.
NSView Focus
// AppKit: make a custom view focusable
class CustomControl: NSView {
override var acceptsFirstResponder: Bool { true }
override var canBecomeKeyView: Bool { true }
override func becomeFirstResponder() -> Bool {
needsDisplay = true
return true
}
override func resignFirstResponder() -> Bool {
needsDisplay = true
return true
}
}Full Keyboard Access
macOS Full Keyboard Access (System Settings → Keyboard → Keyboard Navigation) enables Tab focus on all controls, not just text fields. Test your app with this setting enabled.
Related skills
How it compares
Use focus-engine for Apple-platform focus APIs rather than generic accessibility skills that do not cover tvOS geometric focus or visionOS gaze behavior.
FAQ
What does focus-engine do?
Implement SwiftUI and UIKit keyboard, directional, and scene-level focus behavior.
When should I use focus-engine?
User manages @FocusState, UIFocusGuide, focus sections, or tvOS/visionOS focus routing.
Is focus-engine safe to install?
Review the Security Audits panel on this page before installing in production.