rivetkit-client-swiftui
RivetKit SwiftUI client guidance. Use for SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI, @Actor, rivetKit view modifiers, and SwiftUI bindings.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install rivet-dev-skills-rivetkit-client-swiftui
Repository
Skill path: rivetkit-client-swiftui
RivetKit SwiftUI client guidance. Use for SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI, @Actor, rivetKit view modifiers, and SwiftUI bindings.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: rivet-dev.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install rivetkit-client-swiftui into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/rivet-dev/skills before adding rivetkit-client-swiftui to shared team environments
- Use rivetkit-client-swiftui for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: "rivetkit-client-swiftui"
description: "RivetKit SwiftUI client guidance. Use for SwiftUI apps that connect to Rivet Actors with RivetKitSwiftUI, @Actor, rivetKit view modifiers, and SwiftUI bindings."
---
# RivetKit SwiftUI Client
Use this skill when building SwiftUI apps that connect to Rivet Actors with `RivetKitSwiftUI`.
## Version
RivetKit version: 2.0.42-rc.1
## Install
Add the Swift package dependency and import `RivetKitSwiftUI`:
```swift
// Package.swift
dependencies: [
.package(url: "https://github.com/rivet-dev/rivetkit-swift", from: "2.0.0")
]
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "RivetKitSwiftUI", package: "rivetkit-swift")
]
)
]
```
`RivetKitSwiftUI` re-exports `RivetKitClient` and `SwiftUI`, so a single import covers both.
## Minimal Client
```swift {{"title":"HelloWorldApp.swift"}}
import RivetKitSwiftUI
import SwiftUI
@main
struct HelloWorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:[email protected]")
}
}
}
```
```swift {{"title":"ContentView.swift"}}
import RivetKitSwiftUI
import SwiftUI
struct ContentView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
var body: some View {
VStack(spacing: 16) {
Text("\(count)")
.font(.system(size: 64, weight: .bold, design: .rounded))
Button("Increment") {
counter.send("increment", 1)
}
.disabled(!counter.isConnected)
}
.task {
count = (try? await counter.action("getCount")) ?? 0
}
.onActorEvent(counter, "newCount") { (newCount: Int) in
count = newCount
}
}
}
```
## Actor Options
The `@Actor` property wrapper always uses get-or-create semantics and accepts:
- `name` (required)
- `key` as `String` or `[String]` (required)
- `params` (optional connection parameters)
- `createWithInput` (optional creation input)
- `createInRegion` (optional creation hint)
- `enabled` (toggle connection lifecycle)
```swift
import RivetKitSwiftUI
import SwiftUI
struct ConnParams: Encodable {
let authToken: String
}
struct ChatView: View {
@Actor(
"chatRoom",
key: ["general"],
params: ConnParams(authToken: "jwt-token"),
enabled: true
) private var chat
var body: some View {
Text("Chat: \(chat.connStatus.rawValue)")
}
}
```
## Actions
```swift
import RivetKitSwiftUI
import SwiftUI
struct CounterView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
@State private var name = ""
var body: some View {
VStack {
Text("Count: \(count)")
Text("Name: \(name)")
Button("Fetch") {
Task {
count = try await counter.action("getCount")
name = try await counter.action("rename", "new-name")
}
}
Button("Increment") {
counter.send("increment", 1)
}
}
}
}
```
## Subscribing to Events
```swift
import RivetKitSwiftUI
import SwiftUI
struct GameView: View {
@Actor("game", key: ["game-1"]) private var game
@State private var count = 0
@State private var isGameOver = false
var body: some View {
VStack {
Text("Count: \(count)")
if isGameOver {
Text("Game Over!")
}
}
.onActorEvent(game, "newCount") { (newCount: Int) in
count = newCount
}
.onActorEvent(game, "gameOver") {
isGameOver = true
}
}
}
```
## Async Event Streams
```swift
import RivetKitSwiftUI
import SwiftUI
struct ChatView: View {
@Actor("chatRoom", key: ["general"]) private var chat
@State private var messages: [String] = []
var body: some View {
List(messages, id: \.self) { message in
Text(message)
}
.task {
for await message in chat.events("message", as: String.self) {
messages.append(message)
}
}
}
}
```
## Connection Status
```swift
import RivetKitSwiftUI
import SwiftUI
struct StatusView: View {
@Actor("counter", key: ["my-counter"]) private var counter
@State private var count = 0
var body: some View {
VStack {
Text("Status: \(counter.connStatus.rawValue)")
if counter.connStatus == .connected {
Text("Connected!")
.foregroundStyle(.green)
}
Button("Fetch via Handle") {
Task {
if let handle = counter.handle {
count = try await handle.action("getCount", as: Int.self)
}
}
}
.disabled(!counter.isConnected)
}
}
}
```
## Error Handling
```swift
import RivetKitSwiftUI
import SwiftUI
struct UserView: View {
@Actor("user", key: ["user-123"]) private var user
@State private var errorMessage: String?
@State private var username = ""
var body: some View {
VStack {
TextField("Username", text: $username)
Button("Update Username") {
Task {
do {
let _: String = try await user.action("updateUsername", username)
} catch let error as ActorError {
errorMessage = "\(error.code): \(String(describing: error.metadata))"
}
}
}
if let errorMessage {
Text(errorMessage)
.foregroundStyle(.red)
}
}
.onActorError(user) { error in
errorMessage = "\(error.group).\(error.code): \(error.message)"
}
}
}
```
## Concepts
### Keys
Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:
```swift
import RivetKitSwiftUI
import SwiftUI
struct OrgChatView: View {
@Actor("chatRoom", key: ["org-acme", "general"]) private var room
var body: some View {
Text("Room: \(room.connStatus.rawValue)")
}
}
```
Don't build keys with string interpolation like `"org:\(userId)"` when `userId` contains user data. Use arrays instead to prevent key injection attacks.
### Environment Configuration
Call `.rivetKit(endpoint:)` or `.rivetKit(client:)` once at the root of your view tree:
```swift
// With endpoint string (recommended for most apps)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(endpoint: "https://my-namespace:[email protected]")
}
}
}
// With custom client (for advanced configuration)
@main
struct MyApp: App {
private let client = RivetKitClient(
config: try! ClientConfig(endpoint: "https://api.rivet.dev", token: "pk_...")
)
var body: some Scene {
WindowGroup {
ContentView()
.rivetKit(client: client)
}
}
}
```
When using `.rivetKit(endpoint:)`, the client is created once and cached per endpoint. When using `.rivetKit(client:)`, store the client as a property on `App` (not inside `body`) since SwiftUI can call `body` multiple times.
### Environment Variables
`ClientConfig` reads optional values from environment variables:
- `RIVET_NAMESPACE` - Namespace (can also be in endpoint URL)
- `RIVET_TOKEN` - Authentication token (can also be in endpoint URL)
- `RIVET_RUNNER` - Runner name (defaults to `"default"`)
The endpoint is always required. There is no default endpoint.
### Endpoint Format
Endpoints support URL auth syntax:
```
https://namespace:[email protected]
```
You can also pass the endpoint without auth and provide `RIVET_NAMESPACE` and `RIVET_TOKEN` separately. For serverless deployments, set the endpoint to your app's `/api/rivet` URL. See [Endpoints](/docs/general/endpoints#url-auth-syntax) for details.
## API Reference
### Property Wrapper
- `@Actor(name, key:, params:, createWithInput:, createInRegion:, enabled:)` - SwiftUI property wrapper for actor connections
### View Modifiers
- `.rivetKit(endpoint:)` - Configure client with an endpoint URL (creates cached client)
- `.rivetKit(client:)` - Configure client with a custom instance
- `.onActorEvent(actor, event) { ... }` - Subscribe to actor events (supports 0–5 typed args)
- `.onActorError(actor) { error in ... }` - Handle actor errors
### ActorObservable
- `actor.action(name, args..., as:)` - Async action call
- `actor.send(name, args...)` - Fire-and-forget action
- `actor.events(name, as:)` - AsyncStream of typed events
- `actor.connStatus` - Current connection status
- `actor.isConnected` - Whether connected
- `actor.handle` - Underlying `ActorHandle` (optional)
- `actor.connection` - Underlying `ActorConnection` (optional)
- `actor.error` - Most recent error (optional)
### Types
- `ActorConnStatus` - Connection status enum (`.idle`, `.connecting`, `.connected`, `.disconnected`, `.disposed`)
- `ActorError` - Typed actor errors with `group`, `code`, `message`, `metadata`
## Need More Than the Client?
If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
```bash
npx skills add rivet-dev/skills
```
Then use the `rivetkit` skill for backend guidance.