NotebookFlowKit is a lightweight Swift Package for rendering JSON-defined onboarding, survey, quiz, setup wizard, and learning flows as native SwiftUI screens.
Design a flow in Jupyter or Python, export it as JSON, add the JSON file to your app bundle, and render it without hardcoding every screen.
- Swift Package Manager
- iOS 16+
- macOS 13+
- SwiftUI
- No external dependencies
Add NotebookFlowKit to your app in Xcode:
- Open File > Add Package Dependencies.
- Enter this repository URL.
- Add the
NotebookFlowKitlibrary to your app target.
Or add it to Package.swift:
.package(url: "https://github.com/ChiefVenzox/NotebookFlowKit.git", from: "0.1.0").product(name: "NotebookFlowKit", package: "NotebookFlowKit")Add a JSON file such as starter_flow.json to your app target:
{
"name": "StarterFlow",
"screens": [
{
"id": "welcome",
"type": "text",
"title": "Welcome",
"subtitle": "Let's personalize your app",
"next": "goal"
},
{
"id": "goal",
"type": "choice",
"title": "What is your goal?",
"options": ["Focus", "Fitness", "Learning"],
"next": "name"
},
{
"id": "name",
"type": "input",
"title": "What should we call you?",
"next": "done"
},
{
"id": "done",
"type": "completion",
"title": "You're ready",
"subtitle": "Your setup is complete"
}
]
}Supported screen types:
textchoicemultiChoiceinputcompletion
Aliases such as multi_choice, single_choice, text_input, and done are also accepted.
Use a string next value for linear flows:
{
"id": "goal",
"type": "choice",
"title": "What is your goal?",
"options": ["Focus", "Fitness", "Learning"],
"next": "name"
}Use an object next value for answer-based branching:
{
"id": "goal",
"type": "choice",
"title": "What is your goal?",
"options": ["Focus", "Fitness", "Learning"],
"next": {
"Fitness": "fitness_setup",
"Learning": "learning_setup",
"default": "focus_setup"
}
}For single choice screens, the selected option is matched against the route keys. For multi-choice screens, the first selected option with a matching route wins. default is used when no route matches.
import SwiftUI
import NotebookFlowKit
struct OnboardingView: View {
var body: some View {
NotebookFlowView(fileName: "starter_flow") { result in
print(result.answers)
}
}
}You can also render an in-memory flow:
let flow = NotebookFlow(
name: "StarterFlow",
screens: [
FlowScreen(id: "welcome", type: .text, title: "Welcome", next: "goal"),
FlowScreen(id: "goal", type: .choice, title: "What is your goal?", options: ["Focus", "Learning"], next: "done"),
FlowScreen(id: "done", type: .completion, title: "You're ready")
]
)
NotebookFlowView(flow: flow) { result in
print(result.answers)
}Collected answers are returned as [String: FlowAnswer], keyed by screen ID:
let goal = result.choice("goal")
let name = result.text("name")
let topics = result.choices("topics")You can still inspect raw answers directly:
print(result.answers)NotebookFlowKit shows a basic progress bar by default. Disable it if your app already has its own progress UI:
NotebookFlowView(
fileName: "starter_flow",
showsProgress: false,
onComplete: { result in
print(result.answers)
}
)Use Python/notebook_flow.py in a notebook or Python script:
from notebook_flow import NotebookFlow
flow = NotebookFlow("StarterFlow")
flow.text("welcome", title="Welcome", subtitle="Let's personalize your app", next="goal")
flow.choice("goal", title="What is your goal?", options=["Focus", "Fitness", "Learning"], next="name")
flow.input("name", title="What should we call you?", next="done")
flow.completion("done", title="You're ready", subtitle="Your setup is complete")
flow.export("starter_flow.json")Branching flows can pass a Python dictionary as next:
flow.choice(
"goal",
title="What is your goal?",
options=["Focus", "Fitness", "Learning"],
next={
"Fitness": "fitness_setup",
"Learning": "learning_setup",
"default": "focus_setup",
},
)NotebookFlowKit can generate NotebookFlow values from natural language prompts through a provider-agnostic interface. The package does not include an LLM provider and does not depend on OpenAI, Anthropic, Gemini, or any other AI SDK. You connect your own API client by conforming to NotebookFlowLLMProvider.
The generated text is cleaned, decoded as NotebookFlow JSON, and validated before it is returned for rendering.
import NotebookFlowKit
struct MockLLMProvider: NotebookFlowLLMProvider {
func generate(prompt: String) async throws -> String {
"""
{
"name": "Onboarding",
"screens": [
{
"id": "welcome",
"type": "text",
"title": "Welcome",
"next": "goal"
},
{
"id": "goal",
"type": "choice",
"title": "What is your main goal?",
"options": ["Focus", "Fitness", "Learning"],
"next": "done"
},
{
"id": "done",
"type": "completion",
"title": "You're ready"
}
]
}
"""
}
}
let generator = NotebookFlowAIGenerator(provider: MockLLMProvider())
let flow = try await generator.generateFlow(
from: "Create a short onboarding flow for a personal productivity app."
)You can render the generated flow like any other in-memory flow:
NotebookFlowView(flow: flow) { result in
print(result.answers)
}NotebookFlowLoader validates JSON before rendering:
- the flow must contain at least one screen
- screen IDs must be unique
nextvalues must point to existing screens- choice and multi-choice screens must include options
Examples/starter_flow.jsonExamples/conditional_flow.jsonExamples/DemoNotebookFlowView.swift
Run the package tests:
swift testCreated by ChiefVenzox.
Website: www.hnmlabs.com