Step Flow

Multi-step wizard pattern with state management, navigation, and route generation

Step Definition

The Step class defines a single step in a multi-step workflow. Each step has: - Unique identifier - Title for display - Render function that generates the UI - Optional validation function - Optional data loader for fetching required data - State keys that this step manages


source

Step


def Step(
    id:str, title:str, render:Callable, validate:Optional=None, data_loader:Optional=None, data_keys:List=<factory>,
    can_skip:bool=False, show_back:bool=True, show_cancel:bool=True, next_button_text:str='Continue',
    on_enter:Optional=None, on_leave:Optional=None
)->None:

Definition of a single step in a multi-step workflow.

StepFlow Class

The StepFlow class manages a multi-step workflow. It: - Tracks current step - Manages navigation between steps - Uses a WorkflowStateStore for server-side state persistence - Generates routes automatically - Provides resumability (returns to last valid step on page load)


source

StepFlow


def StepFlow(
    flow_id:str, # Unique identifier for this workflow
    steps:List, # List of step definitions
    state_store:Optional=None, # Storage backend (defaults to InMemoryWorkflowStateStore)
    container_id:str='step-flow-container', # HTML ID for content container
    on_complete:Optional=None, # Completion handler
    show_progress:bool=False, # Whether to show progress indicator
    wrap_in_form:bool=True, # Whether to wrap content + navigation in a form
    debug:bool=False, # Whether to print debug information
):

Manage multi-step workflows with automatic route generation and state management.

Step Management Methods


source

StepFlow.get_step


def get_step(
    step_id:str, # Step identifier
)->Optional: # Step object or None

Get step by ID.


source

StepFlow.get_step_index


def get_step_index(
    step_id:str, # Step identifier
)->Optional: # Step index or None

Get step index by ID.

State Management Methods


source

StepFlow.get_workflow_state


def get_workflow_state(
    sess:Any, # FastHTML session object
)->Dict: # All workflow state

Get all workflow state from state store.


source

StepFlow.update_workflow_state


def update_workflow_state(
    sess:Any, # FastHTML session object
    updates:Dict, # State updates
)->None:

Update workflow state with new values.


source

StepFlow.clear_workflow


def clear_workflow(
    sess:Any, # FastHTML session object
)->None:

Clear all workflow state.

Context and Rendering Methods


source

StepFlow.create_context


def create_context(
    request:Any, # FastHTML request object
    sess:Any, # FastHTML session object
    step:Step, # Current step
)->InteractionContext: # Interaction context for rendering

Create interaction context for a step.


source

StepFlow.render_progress


def render_progress(
    sess:Any, # FastHTML session object
)->FT: # Progress indicator or empty Div

Render progress indicator showing all steps.


source

StepFlow.render_step_content


def render_step_content(
    step_obj:Step, # Step to render
    ctx:InteractionContext, # Interaction context
    next_route:str, # Route for next/submit
    back_route:Optional=None, # Route for back
    cancel_route:Optional=None, # Route for cancel
)->FT: # Complete step content with optional progress and navigation

Render step content with optional progress indicator and navigation.


source

StepFlow.render_navigation


def render_navigation(
    step_id:str, # Current step ID
    next_route:str, # Route for next/submit action
    back_route:Optional=None, # Route for back action
    cancel_route:Optional=None, # Route for cancel action
)->FT: # Navigation button container

Render navigation buttons for a step.

Route Generation


source

StepFlow.create_router


def create_router(
    prefix:str='', # URL prefix for routes (e.g., "/transcription")
)->APIRouter: # APIRouter with generated routes

Create FastHTML router with generated routes for this flow.

Usage Example

Here’s a complete example showing how to create a multi-step workflow. The StepFlow class uses InMemoryWorkflowStateStore by default, so you don’t need to provide a state store for simple use cases.

# Example: Simple 3-step workflow
from fasthtml.common import Div, H2, P, Form, Input, Label

# Define render functions for each step
def render_step1(ctx: InteractionContext):
    """Render first step - collect name."""
    current_name = ctx.get("name", "")
    return Div(
        H2("Step 1: Enter Your Name"),
        Label("Name:"),
        Input(name="name", value=current_name, required=True)
    )

def render_step2(ctx: InteractionContext):
    """Render second step - collect email."""
    name = ctx.get("name", "User")
    current_email = ctx.get("email", "")
    return Div(
        H2(f"Step 2: Hi {name}, enter your email"),
        Label("Email:"),
        Input(name="email", type="email", value=current_email, required=True)
    )

def render_step3(ctx: InteractionContext):
    """Render third step - confirm."""
    name = ctx.get("name", "")
    email = ctx.get("email", "")
    return Div(
        H2("Step 3: Confirm"),
        P(f"Name: {name}"),
        P(f"Email: {email}")
    )

# Define completion handler
def on_complete(state: Dict[str, Any], request):
    """Handle workflow completion."""
    return Div(
        H2("Success!"),
        P(f"Welcome {state.get('name')}!"),
        P(f"We'll email you at {state.get('email')}")
    )

# Create the step flow - uses InMemoryWorkflowStateStore by default
registration_flow = StepFlow(
    flow_id="registration",
    steps=[
        Step(
            id="name",
            title="Enter Name",
            render=render_step1,
            data_keys=["name"]
        ),
        Step(
            id="email",
            title="Enter Email",
            render=render_step2,
            data_keys=["email"]
        ),
        Step(
            id="confirm",
            title="Confirm",
            render=render_step3,
            next_button_text="Complete Registration"
        )
    ],
    on_complete=on_complete,
    show_progress=True
)

# Generate router
registration_router = registration_flow.create_router(prefix="/register")

# In your FastHTML app, register the router:
# from cjm_fasthtml_app_core.core.routing import register_routes
# register_routes(app, registration_router)
#
# Or directly:
# registration_router.to_app(app)
# Test step flow navigation
print(f"Flow has {len(registration_flow.steps)} steps")
print(f"First step: {registration_flow.steps[0].id}")
print(f"Last step: {registration_flow.steps[-1].id}")
print(f"Next after 'name': {registration_flow.get_next_step_id('name')}")
print(f"Previous before 'email': {registration_flow.get_previous_step_id('email')}")
Flow has 3 steps
First step: name
Last step: confirm
Next after 'name': email
Previous before 'email': name