Help us improve
Share bugs, ideas, or general feedback.
From swiftcui
Provides reference guidance for building, changing, explaining, and verifying SwiftCUI console UI apps in Swift, including screens, views, actions, navigation flows, tabs, and scui script validation.
npx claudepluginhub koher/swift-cui --plugin swiftcuiHow this skill is triggered — by the user, by Claude, or both
Slash command
/swiftcui:swiftcuiThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill as reference material when working on SwiftCUI code or when you need to verify a SwiftCUI app through its TCP interface.
Provides best practices and examples for SwiftUI views, components, navigation hierarchies, custom modifiers, responsive layouts with stacks/grids, and state management (@State/@Binding). Use for creating/refactoring iOS UI.
Applies proven SwiftUI UI patterns for navigation, sheets, async state, reusable screens, and app wiring. Useful for creating, refactoring screens or scaffolding new iOS projects.
Guides SwiftUI development for declarative UIs on Apple platforms, covering state management, SF Symbols integration, toolbars, accessibility, performance best practices, and code examples.
Share bugs, ideas, or general feedback.
Use this skill as reference material when working on SwiftCUI code or when you need to verify a SwiftCUI app through its TCP interface.
SwiftCUI is a declarative framework for CUI applications in Swift. A SwiftCUI app runs as a TCP server, and this skill's bundled scui script sends request-response commands such as view and action. That makes the UI easy for AI agents to inspect and verify without relying on interactive terminal sessions.
Keep the UI layer thin and share ViewModels or other application logic between CUI and GUI where possible. That keeps behavior consistent and makes verification cheaper.
Before responding, decide which of these situations you are in:
View, Action, lifecycle, and navigation guidance.TabActionHandler, .actionHandler(.tabs, ...), and navigator.activateStack(...).scui script.view and action requests to a running SwiftCUI app over TCP. Read or run it when you need to verify behavior, reproduce a navigation flow, or give the user exact commands to inspect the current UI state.View.View.body returns display output as a Component, not another View.Action.actions should expose only the operations that are valid in the current state.perform(_:) handles state changes and navigation for one action.navigator.present(), navigator.dismiss(), and navigator.dismissAll()..actionHandler(.tabs, ...) and TabActionHandler.Application, which runs the TCP server used by scui.ViewTreat View as the screen-level unit. Put rendering in body, current affordances in actions, and state transitions in perform(_:). Keeping those roles separate makes the screen easier to understand and easier to verify through scui.
body.if, else, and for directly in body to reflect current state.actions so the rendered hints match the actual UI state.perform(_:) focused on applying the selected action and then updating navigation or state.onAppear() and task()Use lifecycle hooks according to how the work behaves:
onAppear(): initial work required before the first rendered result is returned.task(): ongoing background work that should continue after rendering, such as polling or long-lived observation.Prefer onAppear() for initial loading. Reach for task() only when the work should continue in the background.
This example shows a single screen that renders state, exposes only available actions, and performs navigation after state changes.
import SwiftCUI
struct SearchView: View {
@Environment(\.navigator) var navigator
let viewModel = SearchViewModel()
var body: some Component {
"# Search"
""
if viewModel.isLoading {
"Searching..."
} else if let error = viewModel.errorMessage {
"Error: \(error)"
} else if viewModel.items.isEmpty {
"No results. Use 'search' action."
} else {
for (index, item) in viewModel.items.enumerated() {
"\(index). \(item.name)"
}
}
}
enum Action: ActionProtocol {
case search(query: String)
case detail(index: Int)
var hint: String {
switch self {
case .search: "Search items"
case .detail: "Open detail"
}
}
}
var actions: [Action] {
var actions: [Action] = [.search(query: "...")]
if !viewModel.items.isEmpty {
actions.append(.detail(index: 0))
}
return actions
}
func onAppear() async {
await viewModel.loadInitialData()
}
func perform(_ action: Action) async {
switch action {
case .search(let query):
viewModel.query = query
await viewModel.search()
case .detail(let index):
guard viewModel.items.indices.contains(index) else { return }
navigator.present(DetailView(id: viewModel.items[index].id))
}
}
}
ActionUse an enum for Action in most cases because it maps cleanly to JSON and keeps screen interactions explicit. Implement hint so the rendered action list tells the user or agent what each operation does.
enum Action: ActionProtocol {
case refresh
case search(query: String)
case detail(index: Int)
var hint: String {
switch self {
case .refresh: "Refresh data"
case .search: "Search items"
case .detail: "Open detail"
}
}
}
Get navigator from @Environment(\.navigator) and call it from perform(_:) when the user action should change the stack.
navigator.present(DetailView(id: item.id))
navigator.dismiss()
navigator.dismissAll()
present to move deeper into the current stack.dismiss to go back one screen.dismissAll to return to the root of the current stack.TabActionHandlerUse TabActionHandler when the app needs multiple top-level stacks such as Search, Trending, and User. Keep the responsibilities separated:
tabs: map each tab action to its root view.actions: expose the selectable tabs.perform(_:): switch stacks, usually through navigator.activateStack(...).The tabs themselves are rendered separately, so the handler does not need a body.
This example shows the standard top-level tab handler for three root screens.
import SwiftCUI
struct Tabs: TabActionHandler {
@Environment(\.navigator) var navigator
enum Action: String, ActionProtocol, Hashable {
case search, trending, user
var hint: String {
switch self {
case .search: "Go to Search"
case .trending: "Go to Trending"
case .user: "Go to User"
}
}
}
var tabs: [Action: any View] {
[
.search: SearchView(),
.trending: TrendingView(),
.user: UserView(),
]
}
var actions: [Action] { [.search, .trending, .user] }
func perform(_ action: Action) async {
navigator.activateStack(action)
}
}
Attach the tab handler at the root when you create the app.
let app = Application(rootView: SearchView().actionHandler(.tabs, Tabs()))
try await app.run()
After changing SwiftCUI screens, actions, or tab wiring, verify the behavior when possible. SwiftCUI is designed so the app can be inspected through deterministic command calls instead of an interactive terminal session.
Start the executable target that runs the app:
let app = Application(rootView: SearchView())
try await app.run()
swift run AppName
From any working directory, use the bundled script through ${CLAUDE_SKILL_DIR}:
${CLAUDE_SKILL_DIR}/scripts/scui view
${CLAUDE_SKILL_DIR}/scripts/scui action '{"search":{"query":"swift"}}'
${CLAUDE_SKILL_DIR}/scripts/scui action '{"detail":{"index":0}}'
${CLAUDE_SKILL_DIR}/scripts/scui action --target tabs '"trending"'
Use these checks when they help:
view: inspect the current rendered output.action <json>: trigger a screen action and inspect the resulting state.action --target tabs <json>: switch top-level stacks and verify tab wiring.The default port is 41917. If the app uses a different port, pass --port.
If you want a shorter command name in the current shell, add the skill's script directory to PATH:
export PATH="${CLAUDE_SKILL_DIR}/scripts:$PATH"
Then you can run:
scui view
View and TabActionHandler are already @MainActor, so conforming types usually do not need to repeat it.onAppear() and perform(_:) are async, so direct await is usually the clearest default.Task for button-driven work. Use Task.immediate when synchronous UI control matters.