**Site index:** [https://2plot.dev/llms.txt](https://2plot.dev/llms.txt) — every page on this site, as Markdown.  
**Network index:** [https://2plot.ai/llms.txt](https://2plot.ai/llms.txt) — The 2plot network; start here to discover sibling sites.  
**Sibling sites:** 11 more in The 2plot network — listed in the site index above.  
**Sitemap:** https://2plot.dev/sitemap.xml  

> **Full documentation:** [https://flows.2plot.dev](https://flows.2plot.dev) — the dedicated dash-flows documentation site, with the complete API reference and deeper examples. This page is the quick-start overview.



### Overview

**Dash Flows** is a powerful React Flow 12.3.5+ integration for Plotly Dash that enables developers to create interactive, node-based flow diagrams and visual workflows. Build data pipelines, process flows, org charts, and complex graph visualizations with ease.

#### Key Features

| Feature | Description |
|---------|-------------|
| **6 Node Types** | Input, Output, Default, Group, Toolbar, Resizable, Circle |
| **7 Edge Types** | Bezier, Straight, Step, SmoothStep, Button, Data, AnimatedSVG |
| **Custom Icons** | DashIconify integration with flexible layouts |
| **Auto Layouts** | ELK.js algorithms (layered, force, radial, stress) |
| **Rich Callbacks** | Click, double-click, hover, context menu, selection events |
| **Theming** | Glass, solid, minimal presets with 6 color schemes |
| **Dark Mode** | Full support via Mantine integration |

#### Installation

[Visit GitHub Repo](https://github.com/pip-install-python/dash-flows) · [PyPI](https://pypi.org/project/dash-flows/)

```bash
pip install dash-flows
```

**Optional dependencies for enhanced features:**
```bash
pip install dash-iconify          # For custom icons
pip install dash-mantine-components  # For theming & UI
```

---

### Quick Start

Get started with a minimal working example. The key requirements are:
- Define **nodes** with `id`, `type`, `data`, and `position`
- Define **edges** with `id`, `source`, and `target`
- Set an explicit **height** on the component



```python
# File: docs/dash_flows/introduction.py

"""
Dash Flows - Quick Start Introduction
=====================================
A minimal working example to get started with dash-flows.
"""

import dash_mantine_components as dmc
from dash import html
import dash_flows

# Define a simple 3-node flow
nodes = [
    {
        "id": "start",
        "type": "input",
        "data": {"label": "Start", "sublabel": "Entry point"},
        "position": {"x": 50, "y": 50},
    },
    {
        "id": "process",
        "type": "default",
        "data": {"label": "Process", "sublabel": "Transform data"},
        "position": {"x": 50, "y": 150},
    },
    {
        "id": "end",
        "type": "output",
        "data": {"label": "End", "sublabel": "Output result"},
        "position": {"x": 50, "y": 250},
    },
]

# Connect nodes with edges
edges = [
    {"id": "e1", "source": "start", "target": "process", "animated": True},
    {"id": "e2", "source": "process", "target": "end", "animated": True},
]

component = dmc.Paper(
    [
        dmc.Text(
            "A minimal flow diagram with input, process, and output nodes.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dash_flows.DashFlows(
            id="dash-flows-intro",
            nodes=nodes,
            edges=edges,
            style={"height": "350px"},
            fitView=True,
            showControls=True,
            showMiniMap=True,
        ),
        dmc.Alert(
            [
                dmc.Text("Key requirements:", size="sm", fw=500),
                dmc.List(
                    [
                        dmc.ListItem("Each node needs: id, type, data, position"),
                        dmc.ListItem("Each edge needs: id, source, target"),
                        dmc.ListItem("Container must have explicit height"),
                    ],
                    size="sm",
                ),
            ],
            color="blue",
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)
```


---

### Basic Concepts

Understanding the fundamental building blocks of Dash Flows:

#### Node Structure

Every node requires these properties:

```python
{
    "id": "unique-id",           # Required: Unique identifier
    "type": "default",           # Required: Node type
    "data": {"label": "Text"},   # Required: Node content
    "position": {"x": 100, "y": 50}  # Required: Initial position
}
```

#### Edge Structure

Edges connect nodes together:

```python
{
    "id": "edge-1",              # Required: Unique identifier
    "source": "node-1",          # Required: Source node ID
    "target": "node-2",          # Required: Target node ID
    "animated": True,            # Optional: Animated dashed line
    "label": "Step 1",           # Optional: Edge label
    "type": "smoothstep"         # Optional: Edge style
}
```



```python
# File: docs/dash_flows/basic_nodes_edges.py

"""
Dash Flows - Basic Nodes and Edges
==================================
Understanding the fundamental building blocks of flow diagrams.
"""

import dash_mantine_components as dmc
from dash import html
import dash_flows

# Nodes with detailed structure
nodes = [
    {
        "id": "node-1",
        "type": "default",
        "data": {"label": "Start Node"},
        "position": {"x": 100, "y": 50},
    },
    {
        "id": "node-2",
        "type": "default",
        "data": {"label": "Process A"},
        "position": {"x": 50, "y": 150},
    },
    {
        "id": "node-3",
        "type": "default",
        "data": {"label": "Process B"},
        "position": {"x": 200, "y": 150},
    },
    {
        "id": "node-4",
        "type": "default",
        "data": {"label": "End Node"},
        "position": {"x": 125, "y": 250},
    },
]

# Various edge configurations
edges = [
    {
        "id": "e1-2",
        "source": "node-1",
        "target": "node-2",
        "animated": True,  # Animated dashed line
    },
    {
        "id": "e1-3",
        "source": "node-1",
        "target": "node-3",
    },
    {
        "id": "e2-4",
        "source": "node-2",
        "target": "node-4",
        "label": "Step 1",  # Edge with label
    },
    {
        "id": "e3-4",
        "source": "node-3",
        "target": "node-4",
        "label": "Step 2",
    },
]

component = dmc.Paper(
    [
        dmc.Text(
            "Node and edge structure demonstration with labels and animations.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dmc.SimpleGrid(
            cols={"base": 1, "md": 2},
            spacing="md",
            mb="md",
            children=[
                dmc.Paper(
                    [
                        dmc.Text("Node Structure", fw=600, size="sm", mb="xs"),
                        dmc.Code(
                            block=True,
                            children="""{
    "id": "unique-id",
    "type": "default",
    "data": {"label": "Text"},
    "position": {"x": 100, "y": 50}
}""",
                        ),
                    ],
                    p="sm",
                    withBorder=True,
                ),
                dmc.Paper(
                    [
                        dmc.Text("Edge Structure", fw=600, size="sm", mb="xs"),
                        dmc.Code(
                            block=True,
                            children="""{
    "id": "edge-1",
    "source": "node-1",
    "target": "node-2",
    "animated": True,
    "label": "Step 1"
}""",
                        ),
                    ],
                    p="sm",
                    withBorder=True,
                ),
            ],
        ),
        dash_flows.DashFlows(
            id="dash-flows-basic",
            nodes=nodes,
            edges=edges,
            style={"height": "350px"},
            fitView=True,
            showControls=True,
            showMiniMap=True,
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)
```


---

### Node Types

Dash Flows provides 6 built-in node types, each designed for specific use cases:

| Type | Description | Handles |
|------|-------------|---------|
| `input` | Entry point nodes (green accent) | Source only |
| `output` | Exit point nodes (purple accent) | Target only |
| `default` | Standard processing nodes | Both |
| `group` | Container for child nodes | None (container) |
| `toolbar` | Nodes with floating action toolbar | Both |
| `resizable` | User-resizable nodes | Configurable |
| `circle` | Small animated circular indicators | Both |

---

### Edge Types

Connect nodes with various edge styles:

| Type | Description |
|------|-------------|
| `default` / `bezier` | Smooth curved connections |
| `straight` | Direct line connections |
| `step` | Right-angle with sharp corners |
| `smoothstep` | Right-angle with rounded corners |
| `button` | Edge with interactive delete button |
| `data` | Edge displaying data labels inline |
| `animatedsvg` | Flowing animated shapes |



```python
# File: docs/dash_flows/edge_types.py

"""
Dash Flows - Edge Types
=======================
Demonstrates all available edge connection styles.
"""

import dash_mantine_components as dmc
from dash import html
import dash_flows

# Create nodes for demonstrating different edge types
nodes = [
    # Row 1 - Connection style demos
    {"id": "n1", "type": "default", "data": {"label": "Bezier"}, "position": {"x": 50, "y": 50}},
    {"id": "n2", "type": "default", "data": {"label": "Target"}, "position": {"x": 200, "y": 50}},

    {"id": "n3", "type": "default", "data": {"label": "Straight"}, "position": {"x": 50, "y": 120}},
    {"id": "n4", "type": "default", "data": {"label": "Target"}, "position": {"x": 200, "y": 120}},

    {"id": "n5", "type": "default", "data": {"label": "Step"}, "position": {"x": 50, "y": 190}},
    {"id": "n6", "type": "default", "data": {"label": "Target"}, "position": {"x": 200, "y": 190}},

    {"id": "n7", "type": "default", "data": {"label": "SmoothStep"}, "position": {"x": 50, "y": 260}},
    {"id": "n8", "type": "default", "data": {"label": "Target"}, "position": {"x": 200, "y": 260}},

    # Row 2 - Special edge types
    {"id": "n9", "type": "default", "data": {"label": "Button Edge"}, "position": {"x": 350, "y": 50}},
    {"id": "n10", "type": "default", "data": {"label": "Target"}, "position": {"x": 500, "y": 50}},

    {"id": "n11", "type": "default", "data": {"label": "Data Edge"}, "position": {"x": 350, "y": 140}},
    {"id": "n12", "type": "default", "data": {"label": "Target"}, "position": {"x": 500, "y": 140}},

    {"id": "n13", "type": "default", "data": {"label": "Animated"}, "position": {"x": 350, "y": 230}},
    {"id": "n14", "type": "default", "data": {"label": "Target"}, "position": {"x": 500, "y": 230}},
]

edges = [
    # Standard connection styles
    {
        "id": "e-bezier",
        "source": "n1",
        "target": "n2",
        "type": "default",  # or "bezier" - smooth curve
        "label": "default",
    },
    {
        "id": "e-straight",
        "source": "n3",
        "target": "n4",
        "type": "straight",  # Direct line
        "label": "straight",
    },
    {
        "id": "e-step",
        "source": "n5",
        "target": "n6",
        "type": "step",  # Right angles, sharp corners
        "label": "step",
    },
    {
        "id": "e-smoothstep",
        "source": "n7",
        "target": "n8",
        "type": "smoothstep",  # Right angles, rounded corners
        "label": "smoothstep",
    },
    # Special edge types
    {
        "id": "e-button",
        "source": "n9",
        "target": "n10",
        "type": "button",  # Has delete button
    },
    {
        "id": "e-data",
        "source": "n11",
        "target": "n12",
        "type": "data",  # Shows data inline
        "data": {"label": "42 items"},
    },
    {
        "id": "e-animated",
        "source": "n13",
        "target": "n14",
        "animated": True,  # Animated dashed line
        "style": {"stroke": "#10b981"},
    },
]

component = dmc.Paper(
    [
        dmc.Text(
            "Different edge styles for connecting nodes.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dash_flows.DashFlows(
            id="dash-flows-edge-types",
            nodes=nodes,
            edges=edges,
            style={"height": "380px"},
            fitView=True,
            showControls=True,
            showMiniMap=False,
        ),
        dmc.Table(
            data={
                "head": ["Edge Type", "Description", "Use Case"],
                "body": [
                    ["default/bezier", "Smooth curved line", "General connections"],
                    ["straight", "Direct line", "Simple relationships"],
                    ["step", "Right angles, sharp", "Flowcharts"],
                    ["smoothstep", "Right angles, rounded", "Modern flowcharts"],
                    ["button", "Has delete button", "Editable flows"],
                    ["data", "Shows inline data", "Data pipelines"],
                    ["animated", "Animated dashes", "Active/processing"],
                ],
            },
            striped=True,
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)
```


---

### Custom Icons

Enhance your nodes with custom icons using **DashIconify**. Supports flexible layouts and content-aware sizing.

#### Icon Props

| Prop | Type | Description |
|------|------|-------------|
| `icon` | DashIconify | Icon component to display |
| `iconColor` | str | Background color for icon container |
| `showIcon` | bool | Toggle icon visibility |
| `layout` | str | `"stacked"` (vertical) or `"horizontal"` (two-column) |

#### Content Modes

- **Full Content**: Icon + text = standard layout
- **Icon Only**: Compact square sizing
- **Text Only**: Centered without reserved icon space



```python
# File: docs/dash_flows/custom_icons.py

"""
Dash Flows - Custom Icons
=========================
Using DashIconify for custom node icons with flexible layouts.
"""

import dash_mantine_components as dmc
from dash import html
from dash_iconify import DashIconify
import dash_flows

# Nodes demonstrating icon features
nodes = [
    # Full content with stacked layout (icon above text)
    {
        "id": "db-node",
        "type": "input",
        "data": {
            "icon": DashIconify(icon="mdi:database", width=20, color="white"),
            "label": "Database",
            "body": "PostgreSQL",
            "layout": "stacked",
        },
        "position": {"x": 50, "y": 50},
    },
    # Horizontal layout (icon left, text right)
    {
        "id": "process-node",
        "type": "default",
        "data": {
            "icon": DashIconify(icon="mdi:cog", width=20, color="white"),
            "label": "Transform",
            "body": "Clean data",
            "layout": "horizontal",
        },
        "position": {"x": 50, "y": 150},
    },
    # Icon-only node (compact)
    {
        "id": "icon-only",
        "type": "default",
        "data": {
            "icon": DashIconify(icon="mdi:lightning-bolt", width=24, color="white"),
            "iconColor": "#f59e0b",
            "showIcon": True,
        },
        "position": {"x": 220, "y": 50},
    },
    # Text-only node (centered)
    {
        "id": "text-only",
        "type": "default",
        "data": {
            "label": "Validate",
            "sublabel": "Quality check",
            "showIcon": False,
        },
        "position": {"x": 220, "y": 150},
    },
    # Output with horizontal layout
    {
        "id": "output-node",
        "type": "output",
        "data": {
            "icon": DashIconify(icon="mdi:chart-bar", width=20, color="white"),
            "label": "Dashboard",
            "body": "Visualization",
            "layout": "horizontal",
        },
        "position": {"x": 130, "y": 260},
    },
]

edges = [
    {"id": "e1", "source": "db-node", "target": "process-node", "animated": True},
    {"id": "e2", "source": "icon-only", "target": "text-only", "animated": True},
]

component = dmc.Paper(
    [
        dmc.Text(
            "Custom icons with DashIconify and flexible layout options.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dmc.Group(
            [
                dmc.Badge("Stacked Layout", color="blue", variant="light", size="sm"),
                dmc.Badge("Horizontal Layout", color="green", variant="light", size="sm"),
                dmc.Badge("Icon Only", color="orange", variant="light", size="sm"),
                dmc.Badge("Text Only", color="cyan", variant="light", size="sm"),
            ],
            gap="xs",
            mb="md",
        ),
        dash_flows.DashFlows(
            id="dash-flows-icons",
            nodes=nodes,
            edges=edges,
            style={"height": "380px"},
            fitView=True,
            showControls=True,
            showMiniMap=True,
        ),
        dmc.Accordion(
            [
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Icon Props Reference"),
                        dmc.AccordionPanel(
                            dmc.Table(
                                data={
                                    "head": ["Prop", "Type", "Description"],
                                    "body": [
                                        ["icon", "DashIconify", "Icon component to display"],
                                        ["iconColor", "str", "Background color for icon"],
                                        ["showIcon", "bool", "Toggle icon visibility"],
                                        ["layout", "str", "'stacked' or 'horizontal'"],
                                    ],
                                },
                                striped=True,
                            )
                        ),
                    ],
                    value="props",
                ),
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Content Modes"),
                        dmc.AccordionPanel(
                            dmc.List(
                                [
                                    dmc.ListItem("Full Content: Icon + text = standard sizing"),
                                    dmc.ListItem("Icon Only: Compact square (no label)"),
                                    dmc.ListItem("Text Only: Centered without icon space"),
                                ],
                                size="sm",
                            )
                        ),
                    ],
                    value="modes",
                ),
            ],
            mt="md",
        ),
        dmc.Alert(
            [
                dmc.Text("Browse icons at ", size="sm", span=True),
                dmc.Anchor(
                    "icon-sets.iconify.design",
                    href="https://icon-sets.iconify.design/",
                    target="_blank",
                    size="sm",
                ),
            ],
            color="gray",
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)
```


---

### Node Data Props

Customize node content with these data properties:

| Prop | Type | Description |
|------|------|-------------|
| `label` | str | Primary text (or `title` alias) |
| `sublabel` | str | Secondary text below label |
| `body` | str | Description text |
| `status` | str | Visual state: `"initial"`, `"loading"`, `"success"`, `"error"` |
| `multiline` | bool | Enable text wrapping |

#### Status Indicators

```python
# Loading state - blue pulsing glow
{"data": {"label": "Processing...", "status": "loading"}}

# Success state - green border with checkmark
{"data": {"label": "Complete", "status": "success"}}

# Error state - red border with X badge
{"data": {"label": "Failed", "status": "error"}}
```

---

### Callbacks & Interactivity

Dash Flows provides rich callback support for handling user interactions:

#### Available Output Props

| Prop | Trigger | Data |
|------|---------|------|
| `clickedNode` | Single click | Node object |
| `doubleClickedNode` | Double click | Node object |
| `contextMenuNode` | Right-click | Node + position |
| `hoveredNode` | Mouse enter/leave | Node ID or None |
| `selectedNodes` | Selection change | List of node IDs |
| `selectedEdges` | Selection change | List of edge IDs |
| `lastConnection` | New connection | Source/target info |
| `deletedNodes` | Node deletion | List of deleted IDs |
| `deletedEdges` | Edge deletion | List of deleted IDs |
| `droppedNode` | External drop | Drop position + data |



```python
# File: docs/dash_flows/node_interactions.py

"""
Dash Flows - Node Interactions
==============================
Demonstrates callback support for node events.
"""

import dash_mantine_components as dmc
from dash import html, callback, Input, Output, State
import dash_flows
import json

# Initial nodes for interaction demo
initial_nodes = [
    {"id": "1", "type": "default", "data": {"label": "Click Me"}, "position": {"x": 50, "y": 50}},
    {"id": "2", "type": "default", "data": {"label": "Drag Me"}, "position": {"x": 200, "y": 50}},
    {"id": "3", "type": "default", "data": {"label": "Right-Click"}, "position": {"x": 350, "y": 50}},
    {"id": "4", "type": "default", "data": {"label": "Node 4"}, "position": {"x": 50, "y": 150}},
    {"id": "5", "type": "default", "data": {"label": "Node 5"}, "position": {"x": 200, "y": 150}},
    {"id": "6", "type": "default", "data": {"label": "Node 6"}, "position": {"x": 350, "y": 150}},
]

initial_edges = [
    {"id": "e1-4", "source": "1", "target": "4"},
    {"id": "e2-5", "source": "2", "target": "5"},
    {"id": "e3-6", "source": "3", "target": "6"},
]

component = dmc.Paper(
    [
        dmc.Text(
            "Interact with nodes and observe the callback events below.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dmc.SimpleGrid(
            cols={"base": 1, "md": 2},
            spacing="md",
            children=[
                # Flow canvas
                dmc.Paper(
                    dash_flows.DashFlows(
                        id="dash-flows-interactions",
                        nodes=initial_nodes,
                        edges=initial_edges,
                        style={"height": "300px"},
                        fitView=True,
                        showControls=True,
                        showMiniMap=False,
                        nodesDraggable=True,
                        nodesConnectable=True,
                        elementsSelectable=True,
                        multiSelectionKeyCode="Shift",
                    ),
                    withBorder=True,
                    radius="md",
                    style={"overflow": "hidden"},
                ),
                # Event panels
                dmc.Stack(
                    [
                        dmc.Paper(
                            [
                                dmc.Text("Selected Nodes:", fw=600, size="sm"),
                                html.Pre(
                                    id="dash-flows-selection-info",
                                    children="Select nodes (Shift+click for multi)...",
                                    style={"fontSize": "11px", "margin": 0, "maxHeight": "60px", "overflow": "auto"},
                                ),
                            ],
                            p="sm",
                            withBorder=True,
                        ),
                        dmc.Paper(
                            [
                                dmc.Text("Context Menu (Right-Click):", fw=600, size="sm"),
                                html.Pre(
                                    id="dash-flows-context-info",
                                    children="Right-click a node...",
                                    style={"fontSize": "11px", "margin": 0, "maxHeight": "60px", "overflow": "auto"},
                                ),
                            ],
                            p="sm",
                            withBorder=True,
                        ),
                        dmc.Paper(
                            [
                                dmc.Text("Node Positions:", fw=600, size="sm"),
                                html.Pre(
                                    id="dash-flows-positions-info",
                                    children="Drag a node to see updates...",
                                    style={"fontSize": "11px", "margin": 0, "maxHeight": "80px", "overflow": "auto"},
                                ),
                            ],
                            p="sm",
                            withBorder=True,
                        ),
                    ],
                    gap="xs",
                ),
            ],
        ),
        dmc.Accordion(
            [
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Available Callback Props"),
                        dmc.AccordionPanel(
                            dmc.Table(
                                data={
                                    "head": ["Output Prop", "Trigger", "Data"],
                                    "body": [
                                        ["clickedNode", "Single click", "Node object"],
                                        ["doubleClickedNode", "Double click", "Node object"],
                                        ["contextMenuNode", "Right-click", "Node + position"],
                                        ["hoveredNode", "Mouse enter/leave", "Node ID"],
                                        ["selectedNodes", "Selection change", "List of IDs"],
                                        ["selectedEdges", "Selection change", "List of IDs"],
                                        ["lastConnection", "New connection", "Source/target"],
                                        ["deletedNodes", "Deletion", "Deleted IDs"],
                                    ],
                                },
                                striped=True,
                            )
                        ),
                    ],
                    value="props",
                ),
            ],
            mt="md",
        ),
        dmc.Alert(
            [
                dmc.Text("Tips:", fw=500, size="sm"),
                dmc.List(
                    [
                        dmc.ListItem("Hold Shift and click to multi-select nodes"),
                        dmc.ListItem("Drag on canvas to create selection box"),
                        dmc.ListItem("Use prevent_initial_call=True in callbacks"),
                    ],
                    size="sm",
                ),
            ],
            color="blue",
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)


# Callbacks for the interaction demo
@callback(
    Output("dash-flows-selection-info", "children"),
    Input("dash-flows-interactions", "selectedNodes"),
    Input("dash-flows-interactions", "selectedEdges"),
)
def update_selection(nodes, edges):
    """Show currently selected nodes and edges."""
    node_ids = []
    if nodes:
        for n in nodes:
            if isinstance(n, dict) and "id" in n:
                node_ids.append(n["id"])
            elif isinstance(n, str):
                node_ids.append(n)

    edge_ids = []
    if edges:
        for e in edges:
            if isinstance(e, dict) and "id" in e:
                edge_ids.append(e["id"])
            elif isinstance(e, str):
                edge_ids.append(e)

    return json.dumps({"nodes": node_ids, "edges": edge_ids}, indent=2)


@callback(
    Output("dash-flows-context-info", "children"),
    Input("dash-flows-interactions", "contextMenuNode"),
    prevent_initial_call=True,
)
def update_context_menu(context_data):
    """Show context menu event data."""
    if not context_data:
        return "Right-click a node..."
    return json.dumps(context_data, indent=2)


@callback(
    Output("dash-flows-positions-info", "children"),
    Input("dash-flows-interactions", "nodes"),
    prevent_initial_call=True,
)
def update_positions(nodes):
    """Show node positions after dragging."""
    if not nodes:
        return "No nodes"
    positions = {n["id"]: n["position"] for n in nodes[:3]}  # Show first 3
    return json.dumps(positions, indent=2)
```


---

### Automatic Layouts (ELK)

Dash Flows integrates **ELK.js** for automatic graph layout. Pass layout options as a JSON string:

#### Layout Algorithms

| Algorithm | Best For |
|-----------|----------|
| `layered` | Hierarchical/directed graphs |
| `org.eclipse.elk.force` | Organic, force-directed |
| `org.eclipse.elk.radial` | Concentric circles |
| `org.eclipse.elk.stress` | Balanced edge lengths |

#### Direction Options (Layered)

- `DOWN` - Top to bottom
- `UP` - Bottom to top
- `RIGHT` - Left to right
- `LEFT` - Right to left

```python
import json

layout_options = json.dumps({
    "elk.algorithm": "layered",
    "elk.direction": "DOWN",
    "elk.spacing.nodeNode": 50,
    "elk.layered.spacing.nodeNodeBetweenLayers": 80,
})

dash_flows.DashFlows(
    layoutOptions=layout_options,
    # ... other props
)
```



```python
# File: docs/dash_flows/elk_layouts.py

"""
Dash Flows - ELK Automatic Layouts
==================================
Demonstrates automatic graph layout algorithms using ELK.js.
"""

import dash_mantine_components as dmc
from dash import html, callback, Input, Output, State
import dash_flows
import json

# Create a graph to demonstrate layouts
initial_nodes = [
    {"id": "1", "type": "input", "data": {"label": "Start"}, "position": {"x": 0, "y": 0}},
    {"id": "2", "type": "default", "data": {"label": "Step A"}, "position": {"x": 0, "y": 0}},
    {"id": "3", "type": "default", "data": {"label": "Step B"}, "position": {"x": 0, "y": 0}},
    {"id": "4", "type": "default", "data": {"label": "Step C"}, "position": {"x": 0, "y": 0}},
    {"id": "5", "type": "default", "data": {"label": "Step D"}, "position": {"x": 0, "y": 0}},
    {"id": "6", "type": "default", "data": {"label": "Merge"}, "position": {"x": 0, "y": 0}},
    {"id": "7", "type": "output", "data": {"label": "End"}, "position": {"x": 0, "y": 0}},
]

initial_edges = [
    {"id": "e1-2", "source": "1", "target": "2"},
    {"id": "e1-3", "source": "1", "target": "3"},
    {"id": "e2-4", "source": "2", "target": "4"},
    {"id": "e3-5", "source": "3", "target": "5"},
    {"id": "e4-6", "source": "4", "target": "6"},
    {"id": "e5-6", "source": "5", "target": "6"},
    {"id": "e6-7", "source": "6", "target": "7"},
]

# Layout presets
layout_presets = {
    "layered-down": {
        "elk.algorithm": "layered",
        "elk.direction": "DOWN",
        "elk.spacing.nodeNode": 50,
        "elk.layered.spacing.nodeNodeBetweenLayers": 80,
    },
    "layered-right": {
        "elk.algorithm": "layered",
        "elk.direction": "RIGHT",
        "elk.spacing.nodeNode": 50,
        "elk.layered.spacing.nodeNodeBetweenLayers": 120,
    },
    "force": {
        "elk.algorithm": "org.eclipse.elk.force",
        "elk.force.iterations": 300,
        "elk.spacing.nodeNode": 80,
    },
    "radial": {
        "elk.algorithm": "org.eclipse.elk.radial",
        "elk.radial.radius": 150,
    },
}

component = dmc.Paper(
    [
        dmc.Text(
            "Apply automatic layout algorithms to arrange nodes.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dmc.Group(
            [
                dmc.Select(
                    id="dash-flows-elk-layout-select",
                    label="Layout Algorithm",
                    data=[
                        {"value": "layered-down", "label": "Layered (Top to Bottom)"},
                        {"value": "layered-right", "label": "Layered (Left to Right)"},
                        {"value": "force", "label": "Force-Directed"},
                        {"value": "radial", "label": "Radial"},
                    ],
                    value="layered-down",
                    style={"width": "220px"},
                ),
                dmc.Button(
                    "Apply Layout",
                    id="dash-flows-elk-apply-btn",
                    variant="filled",
                    mt="auto",
                ),
            ],
            gap="md",
            mb="md",
        ),
        dash_flows.DashFlows(
            id="dash-flows-elk",
            nodes=initial_nodes,
            edges=initial_edges,
            style={"height": "350px"},
            fitView=True,
            showControls=True,
            showMiniMap=True,
            layoutOptions=json.dumps(layout_presets["layered-down"]),
        ),
        dmc.Accordion(
            [
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Current Layout Options"),
                        dmc.AccordionPanel(
                            html.Pre(
                                id="dash-flows-elk-options-display",
                                children=json.dumps(layout_presets["layered-down"], indent=2),
                                style={"fontSize": "11px", "margin": 0},
                            )
                        ),
                    ],
                    value="options",
                ),
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Layout Algorithms"),
                        dmc.AccordionPanel(
                            dmc.Table(
                                data={
                                    "head": ["Algorithm", "Best For"],
                                    "body": [
                                        ["layered", "Hierarchical/directed graphs"],
                                        ["org.eclipse.elk.force", "Organic, force-directed"],
                                        ["org.eclipse.elk.radial", "Concentric circles"],
                                        ["org.eclipse.elk.stress", "Balanced edge lengths"],
                                    ],
                                },
                                striped=True,
                            )
                        ),
                    ],
                    value="algorithms",
                ),
            ],
            value="options",
            mt="md",
        ),
        dmc.Alert(
            [
                dmc.Text("Important:", fw=500, size="sm"),
                dmc.Text(
                    "layoutOptions must be a JSON string. Use json.dumps() to convert Python dicts.",
                    size="sm",
                ),
            ],
            color="yellow",
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)


@callback(
    Output("dash-flows-elk", "layoutOptions"),
    Output("dash-flows-elk-options-display", "children"),
    Input("dash-flows-elk-apply-btn", "n_clicks"),
    State("dash-flows-elk-layout-select", "value"),
    prevent_initial_call=True,
)
def apply_layout(n_clicks, layout_type):
    """Apply the selected layout algorithm."""
    options = layout_presets.get(layout_type, layout_presets["layered-down"])
    return json.dumps(options), json.dumps(options, indent=2)
```


---

### Theming & Dark Mode

Customize the visual appearance with theme presets and color schemes:

#### Theme Presets

| Preset | Description |
|--------|-------------|
| `glass` | Glass morphism with blur effects (default) |
| `solid` | Solid backgrounds |
| `minimal` | Clean, minimal styling |

#### Color Schemes

`default`, `ocean`, `forest`, `sunset`, `midnight`, `rose`

#### Dark Mode

Integrates with Dash Mantine Components for dark mode support:

```python
dmc.MantineProvider([
    dash_flows.DashFlows(
        colorMode="dark",  # or "light" or "system"
        # ... other props
    )
])
```



```python
# File: docs/dash_flows/theming_dark_mode.py

"""
Dash Flows - Theming & Dark Mode
================================
Demonstrates theme presets, color schemes, and dark mode integration.
"""

import dash_mantine_components as dmc
from dash import html, callback, Input, Output
import dash_flows

# Sample nodes for theming demo
nodes = [
    {
        "id": "input-1",
        "type": "input",
        "data": {"label": "Data Source", "sublabel": "Input"},
        "position": {"x": 50, "y": 50},
    },
    {
        "id": "process-1",
        "type": "default",
        "data": {"label": "Transform", "sublabel": "Process"},
        "position": {"x": 50, "y": 150},
    },
    {
        "id": "process-2",
        "type": "default",
        "data": {"label": "Validate", "sublabel": "Check"},
        "position": {"x": 200, "y": 150},
    },
    {
        "id": "output-1",
        "type": "output",
        "data": {"label": "Dashboard", "sublabel": "Output"},
        "position": {"x": 125, "y": 250},
    },
]

edges = [
    {"id": "e1", "source": "input-1", "target": "process-1", "animated": True},
    {"id": "e2", "source": "input-1", "target": "process-2", "animated": True},
    {"id": "e3", "source": "process-1", "target": "output-1"},
    {"id": "e4", "source": "process-2", "target": "output-1"},
]

component = dmc.Paper(
    [
        dmc.Text(
            "Customize appearance with theme presets and color schemes.",
            c="dimmed",
            size="sm",
            mb="md",
        ),
        dmc.SimpleGrid(
            cols={"base": 1, "sm": 3},
            spacing="md",
            mb="md",
            children=[
                dmc.Select(
                    id="dash-flows-theme-preset",
                    label="Theme Preset",
                    data=[
                        {"value": "glass", "label": "Glass (Default)"},
                        {"value": "solid", "label": "Solid"},
                        {"value": "minimal", "label": "Minimal"},
                    ],
                    value="glass",
                ),
                dmc.Select(
                    id="dash-flows-color-scheme",
                    label="Color Scheme",
                    data=[
                        {"value": "default", "label": "Default"},
                        {"value": "ocean", "label": "Ocean"},
                        {"value": "forest", "label": "Forest"},
                        {"value": "sunset", "label": "Sunset"},
                        {"value": "midnight", "label": "Midnight"},
                        {"value": "rose", "label": "Rose"},
                    ],
                    value="default",
                ),
                dmc.Select(
                    id="dash-flows-color-mode",
                    label="Color Mode",
                    data=[
                        {"value": "light", "label": "Light"},
                        {"value": "dark", "label": "Dark"},
                    ],
                    value="light",
                ),
            ],
        ),
        dash_flows.DashFlows(
            id="dash-flows-theming",
            nodes=nodes,
            edges=edges,
            style={"height": "350px"},
            fitView=True,
            showControls=True,
            showMiniMap=True,
            themePreset="glass",
            colorScheme="default",
            colorMode="light",
        ),
        dmc.Accordion(
            [
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Theme Presets"),
                        dmc.AccordionPanel(
                            dmc.Table(
                                data={
                                    "head": ["Preset", "Description"],
                                    "body": [
                                        ["glass", "Glass morphism with blur effects"],
                                        ["solid", "Solid, opaque backgrounds"],
                                        ["minimal", "Clean, minimal styling"],
                                    ],
                                },
                                striped=True,
                            )
                        ),
                    ],
                    value="presets",
                ),
                dmc.AccordionItem(
                    [
                        dmc.AccordionControl("Color Schemes"),
                        dmc.AccordionPanel(
                            dmc.Group(
                                [
                                    dmc.Badge("default", color="blue", variant="filled"),
                                    dmc.Badge("ocean", color="cyan", variant="filled"),
                                    dmc.Badge("forest", color="green", variant="filled"),
                                    dmc.Badge("sunset", color="orange", variant="filled"),
                                    dmc.Badge("midnight", color="indigo", variant="filled"),
                                    dmc.Badge("rose", color="pink", variant="filled"),
                                ],
                                gap="xs",
                            )
                        ),
                    ],
                    value="schemes",
                ),
            ],
            mt="md",
        ),
        dmc.Alert(
            [
                dmc.Text("Dark Mode Integration:", fw=500, size="sm"),
                dmc.Text(
                    "For full dark mode support, wrap your app in dmc.MantineProvider "
                    "and use colorMode='dark' on DashFlows.",
                    size="sm",
                ),
            ],
            color="gray",
            mt="md",
        ),
    ],
    p="md",
    withBorder=True,
    radius="md",
)


@callback(
    Output("dash-flows-theming", "themePreset"),
    Output("dash-flows-theming", "colorScheme"),
    Output("dash-flows-theming", "colorMode"),
    Input("dash-flows-theme-preset", "value"),
    Input("dash-flows-color-scheme", "value"),
    Input("dash-flows-color-mode", "value"),
)
def update_theme(preset, scheme, mode):
    """Update the flow theme based on selections."""
    return preset or "glass", scheme or "default", mode or "light"
```


---

### Controls & Display

#### Built-in UI Components

| Prop | Default | Description |
|------|---------|-------------|
| `showControls` | True | Zoom +/- and fit view buttons |
| `showMiniMap` | True | Overview minimap |
| `showBackground` | True | Canvas background pattern |
| `showDevTools` | False | Debug information panel |

#### Background Patterns

```python
dash_flows.DashFlows(
    showBackground=True,
    backgroundVariant="dots",  # "dots", "lines", or "cross"
    backgroundGap=16,          # Pattern spacing
)
```

#### Control Positions

```python
controlsPosition="bottom-left"   # Default
miniMapPosition="bottom-right"   # Default
```

---

### Viewport Control

Programmatically control the viewport:

```python
# Fit all nodes in view
viewportAction={"type": "fitView", "options": {"padding": 0.2}}

# Zoom in/out
viewportAction={"type": "zoomIn"}
viewportAction={"type": "zoomOut"}

# Center on specific coordinates
viewportAction={"type": "setCenter", "x": 200, "y": 150, "zoom": 1.5}
```

#### Viewport Props

| Prop | Default | Description |
|------|---------|-------------|
| `minZoom` | 0.5 | Minimum zoom level |
| `maxZoom` | 2 | Maximum zoom level |
| `fitView` | False | Auto-fit on initial render |
| `snapToGrid` | False | Snap nodes to grid |
| `snapGrid` | [15, 15] | Grid size for snapping |

---

### Interaction Props

Control how users interact with the flow:

| Prop | Default | Description |
|------|---------|-------------|
| `nodesDraggable` | True | Allow dragging nodes |
| `nodesConnectable` | True | Allow creating connections |
| `elementsSelectable` | True | Allow selecting elements |
| `panOnDrag` | True | Pan canvas by dragging |
| `zoomOnScroll` | True | Zoom with scroll wheel |
| `zoomOnPinch` | True | Zoom with pinch gesture |
| `zoomOnDoubleClick` | True | Zoom on double-click |
| `multiSelectionKeyCode` | "Shift" | Key for multi-select |
| `deleteKeyCode` | "Backspace" | Key to delete selected |

---

### Props Reference

#### Core Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `id` | str | Required | Component ID for callbacks |
| `nodes` | list | `[]` | Array of node objects |
| `edges` | list | `[]` | Array of edge objects |
| `style` | dict | `{}` | Container style (height required!) |

#### Display Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `fitView` | bool | False | Auto-fit on mount |
| `showControls` | bool | True | Show viewport controls |
| `showMiniMap` | bool | True | Show minimap |
| `showBackground` | bool | True | Show background pattern |
| `backgroundVariant` | str | "dots" | "dots", "lines", "cross" |
| `controlsPosition` | str | "bottom-left" | Control panel position |
| `miniMapPosition` | str | "bottom-right" | Minimap position |

#### Theme Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `themePreset` | str | "glass" | "glass", "solid", "minimal" |
| `colorScheme` | str | "default" | Color scheme name |
| `colorMode` | str | "light" | "light", "dark", "system" |

#### Layout Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `layoutOptions` | str | None | ELK layout JSON string |
| `defaultViewport` | dict | None | Initial viewport {x, y, zoom} |
| `minZoom` | float | 0.5 | Minimum zoom level |
| `maxZoom` | float | 2 | Maximum zoom level |

#### Callback Output Props

| Prop | Type | Description |
|------|------|-------------|
| `selectedNodes` | list | Currently selected node IDs |
| `selectedEdges` | list | Currently selected edge IDs |
| `clickedNode` | dict | Last clicked node |
| `doubleClickedNode` | dict | Last double-clicked node |
| `contextMenuNode` | dict | Right-clicked node with position |
| `hoveredNode` | dict | Currently hovered node |
| `lastConnection` | dict | Last created connection |
| `deletedNodes` | list | Recently deleted node IDs |
| `deletedEdges` | list | Recently deleted edge IDs |
| `viewport` | dict | Current viewport state |

---

### New in 1.2.0

#### Smart Handle Positioning



```python
# File: docs/dash_flows/smart_handles.py

"""Smart Handle Positioning — auto-routes edges to closest node side."""
import dash_mantine_components as dmc
import dash_flows
from dash_iconify import DashIconify

nodes = [
    {"id": "a", "type": "input", "data": {"label": "Data Source"}, "position": {"x": 50, "y": 50}},
    {"id": "b", "type": "default", "data": {"label": "Transform"}, "position": {"x": 300, "y": 50}},
    {"id": "c", "type": "default", "data": {"label": "Validate"}, "position": {"x": 150, "y": 200}},
    {"id": "d", "type": "output", "data": {"label": "Output"}, "position": {"x": 400, "y": 200}},
    {"id": "e", "type": "default", "data": {"label": "Cache"}, "position": {"x": 50, "y": 300}},
]
edges = [
    {"id": "e1", "source": "a", "target": "b", "animated": True},
    {"id": "e2", "source": "a", "target": "c", "animated": True},
    {"id": "e3", "source": "b", "target": "d", "animated": True},
    {"id": "e4", "source": "c", "target": "d", "animated": True},
    {"id": "e5", "source": "c", "target": "e", "animated": True},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Smart Handle Positioning", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text("Edges automatically route to the closest side of each node. Drag nodes around to see handles reposition.", size="sm", c="dimmed", mb="md"),
    dash_flows.DashFlows(
        id="df-smart-handles",
        nodes=nodes,
        edges=edges,
        smartHandles=True,
        style={"height": "400px"},
        fitView=True,
        showControls=True,
        showMiniMap=True,
    ),
], p="md", withBorder=True, radius="md")
```


---

#### Floating Edges



```python
# File: docs/dash_flows/floating_edges.py

"""Floating Edges — connect to nearest point on node border."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {"id": "1", "type": "input", "data": {"label": "API Gateway"}, "position": {"x": 0, "y": 100}},
    {"id": "2", "type": "default", "data": {"label": "Auth Service"}, "position": {"x": 250, "y": 0}},
    {"id": "3", "type": "default", "data": {"label": "User Service"}, "position": {"x": 250, "y": 200}},
    {"id": "4", "type": "default", "data": {"label": "Database"}, "position": {"x": 500, "y": 100}},
    {"id": "5", "type": "output", "data": {"label": "Response"}, "position": {"x": 700, "y": 100}},
]
edges = [
    {"id": "e1", "source": "1", "target": "2", "type": "floating", "animated": True},
    {"id": "e2", "source": "1", "target": "3", "type": "floating", "animated": True},
    {"id": "e3", "source": "2", "target": "4", "type": "floating"},
    {"id": "e4", "source": "3", "target": "4", "type": "floating"},
    {"id": "e5", "source": "4", "target": "5", "type": "floating", "animated": True},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Floating Edges", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Edges connect to the nearest point on each node's border instead of fixed handle positions. "
        "Drag nodes to see edges dynamically recalculate their connection points.",
        size="sm", c="dimmed", mb="md",
    ),
    dash_flows.DashFlows(
        id="df-floating-edges",
        nodes=nodes,
        edges=edges,
        style={"height": "380px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Code('{"type": "floating"}  # Set on any edge to enable border intersection', block=True),
], p="md", withBorder=True, radius="md")
```


---

#### Helper Lines (Alignment Guides)



```python
# File: docs/dash_flows/helper_lines.py

"""Helper Lines — alignment guides when dragging nodes."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {"id": "1", "type": "input", "data": {"label": "Node A"}, "position": {"x": 50, "y": 50}},
    {"id": "2", "type": "default", "data": {"label": "Node B"}, "position": {"x": 250, "y": 50}},
    {"id": "3", "type": "default", "data": {"label": "Node C"}, "position": {"x": 150, "y": 180}},
    {"id": "4", "type": "output", "data": {"label": "Node D"}, "position": {"x": 350, "y": 180}},
]
edges = [
    {"id": "e1", "source": "1", "target": "3"},
    {"id": "e2", "source": "2", "target": "3"},
    {"id": "e3", "source": "3", "target": "4"},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Helper Lines (Alignment Guides)", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Blue alignment guides appear when dragging nodes near other nodes' edges. "
        "Nodes snap to alignment within the threshold distance.",
        size="sm", c="dimmed", mb="md",
    ),
    dash_flows.DashFlows(
        id="df-helper-lines",
        nodes=nodes,
        edges=edges,
        helperLines=True,
        helperLineThreshold=5,
        style={"height": "350px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Alert(
        "Drag any node slowly near another to see the blue alignment guides appear and snap.",
        color="blue", variant="light", mt="md",
    ),
], p="md", withBorder=True, radius="md")
```


---

#### Sub-flows (Collapsible Groups)



```python
# File: docs/dash_flows/subflows.py

"""Sub-flows — collapsible group nodes."""
import dash_mantine_components as dmc
import dash_flows
from dash import callback, Input, Output, no_update

nodes = [
    {"id": "source", "type": "input", "data": {"label": "Data Source"}, "position": {"x": 50, "y": 130}},
    # Group container
    {
        "id": "pipeline",
        "type": "group",
        "data": {"label": "ETL Pipeline", "collapsedWidth": 200, "collapsedHeight": 52},
        "position": {"x": 250, "y": 50},
        "style": {"width": 350, "height": 250},
    },
    # Children inside group
    {"id": "extract", "type": "default", "data": {"label": "Extract"}, "position": {"x": 30, "y": 40}, "parentId": "pipeline", "extent": "parent"},
    {"id": "transform", "type": "default", "data": {"label": "Transform"}, "position": {"x": 30, "y": 130}, "parentId": "pipeline", "extent": "parent"},
    {"id": "load", "type": "default", "data": {"label": "Load"}, "position": {"x": 180, "y": 85}, "parentId": "pipeline", "extent": "parent"},
    # Output
    {"id": "dashboard", "type": "output", "data": {"label": "Dashboard"}, "position": {"x": 700, "y": 130}},
]
edges = [
    {"id": "e1", "source": "source", "target": "extract", "animated": True},
    {"id": "e2", "source": "extract", "target": "transform"},
    {"id": "e3", "source": "transform", "target": "load"},
    {"id": "e4", "source": "load", "target": "dashboard", "animated": True},
]

@callback(
    Output("df-subflows", "toggleCollapseNode"),
    Input("df-subflows", "doubleClickedNode"),
    prevent_initial_call=True,
)
def toggle_group_collapse(double_clicked):
    if double_clicked and double_clicked.get("type") == "group":
        return double_clicked["id"]
    return no_update


component = dmc.Paper([
    dmc.Group([
        dmc.Text("Sub-flows (Collapsible Groups)", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Double-click the 'ETL Pipeline' group to collapse/expand it. "
        "Child nodes hide when collapsed; external edges remain connected at the group level.",
        size="sm", c="dimmed", mb="md",
    ),
    dash_flows.DashFlows(
        id="df-subflows",
        nodes=nodes,
        edges=edges,
        style={"height": "380px"},
        fitView=True,
        showControls=True,
        showMiniMap=True,
    ),
    dmc.Alert(
        "Group nodes support collapsedWidth/collapsedHeight for compact dimensions when collapsed.",
        color="blue", variant="light", mt="md",
    ),
], p="md", withBorder=True, radius="md")
```


---

#### Undo / Redo



```python
# File: docs/dash_flows/undo_redo.py

"""Undo/Redo — history tracking for node/edge changes."""
import dash_mantine_components as dmc
from dash import html, callback, Input, Output, State
from dash_iconify import DashIconify
import dash_flows

nodes = [
    {"id": "a", "type": "input", "data": {"label": "Input A"}, "position": {"x": 50, "y": 50}},
    {"id": "b", "type": "default", "data": {"label": "Process"}, "position": {"x": 250, "y": 50}},
    {"id": "c", "type": "output", "data": {"label": "Output"}, "position": {"x": 450, "y": 50}},
]
edges = [
    {"id": "e1", "source": "a", "target": "b", "animated": True},
    {"id": "e2", "source": "b", "target": "c", "animated": True},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Undo / Redo", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Drag nodes, create connections, or delete elements — then undo/redo with the buttons below.",
        size="sm", c="dimmed", mb="md",
    ),
    dmc.Group([
        dmc.Button("Undo", id="df-undo-btn", leftSection=DashIconify(icon="tabler:arrow-back-up", width=16), variant="light", size="sm"),
        dmc.Button("Redo", id="df-redo-btn", leftSection=DashIconify(icon="tabler:arrow-forward-up", width=16), variant="light", size="sm"),
        html.Div(id="df-undo-status"),
    ], mb="md"),
    dash_flows.DashFlows(
        id="df-undo-redo",
        nodes=nodes,
        edges=edges,
        enableUndoRedo=True,
        undoRedoMaxHistory=30,
        style={"height": "300px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Alert(
        "Try: drag a node, then click Undo to reverse. Tracks position changes, additions, and deletions.",
        color="blue", variant="light", mt="md",
    ),
], p="md", withBorder=True, radius="md")


@callback(
    Output("df-undo-redo", "undoRedoAction"),
    Input("df-undo-btn", "n_clicks"),
    Input("df-redo-btn", "n_clicks"),
    prevent_initial_call=True,
)
def handle_undo_redo(undo_clicks, redo_clicks):
    from dash import ctx
    if ctx.triggered_id == "df-undo-btn":
        return {"action": "undo"}
    return {"action": "redo"}


@callback(
    Output("df-undo-status", "children"),
    Input("df-undo-redo", "undoRedoState"),
    prevent_initial_call=True,
)
def show_undo_state(state):
    if not state:
        return ""
    return dmc.Group([
        dmc.Badge(f"Undo: {state.get('undoCount', 0)}", color="gray", variant="light", size="sm"),
        dmc.Badge(f"Redo: {state.get('redoCount', 0)}", color="gray", variant="light", size="sm"),
    ], gap="xs")
```


---

#### Additional 1.2.0 Features

The following features are also available — view source code for implementation details:

**Add Node on Edge Drop** — Drag a connection to empty canvas to create a node:



```python
# File: docs/dash_flows/add_node_drop.py

"""Add Node on Edge Drop — drag a connection to empty canvas to create a node."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {"id": "start", "type": "input", "data": {"label": "Start"}, "position": {"x": 50, "y": 100}},
    {"id": "step-1", "type": "default", "data": {"label": "Step 1"}, "position": {"x": 300, "y": 100}},
]
edges = [
    {"id": "e1", "source": "start", "target": "step-1", "animated": True},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Add Node on Edge Drop", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Drag a connection from any handle and drop it on empty canvas to create a new node. "
        "The new node is automatically connected to the source.",
        size="sm", c="dimmed", mb="md",
    ),
    dash_flows.DashFlows(
        id="df-add-node-drop",
        nodes=nodes,
        edges=edges,
        addNodeOnEdgeDrop=True,
        style={"height": "350px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Alert(
        "Drag from the bottom handle of any node into empty space, then release to create a new node.",
        color="indigo", variant="light", mt="md",
    ),
], p="md", withBorder=True, radius="md")
```


**Animated Layout Transitions** — Smooth ELK layout changes with callbacks:



```python
# File: docs/dash_flows/animated_layout.py

"""Animated Layout Transitions — smooth ELK layout changes."""
import json
import dash_mantine_components as dmc
from dash import callback, Input, Output
import dash_flows

nodes = [
    {"id": "1", "type": "input", "data": {"label": "Source"}, "position": {"x": 0, "y": 0}},
    {"id": "2", "type": "default", "data": {"label": "Parse"}, "position": {"x": 200, "y": 0}},
    {"id": "3", "type": "default", "data": {"label": "Transform"}, "position": {"x": 100, "y": 120}},
    {"id": "4", "type": "default", "data": {"label": "Validate"}, "position": {"x": 300, "y": 120}},
    {"id": "5", "type": "default", "data": {"label": "Enrich"}, "position": {"x": 200, "y": 240}},
    {"id": "6", "type": "output", "data": {"label": "Output"}, "position": {"x": 200, "y": 360}},
]
edges = [
    {"id": "e1", "source": "1", "target": "2"},
    {"id": "e2", "source": "1", "target": "3"},
    {"id": "e3", "source": "2", "target": "4"},
    {"id": "e4", "source": "3", "target": "5"},
    {"id": "e5", "source": "4", "target": "5"},
    {"id": "e6", "source": "5", "target": "6", "animated": True},
]

LAYOUTS = {
    "layered-down": json.dumps({"elk.algorithm": "layered", "elk.direction": "DOWN", "elk.spacing.nodeNode": "60"}),
    "layered-right": json.dumps({"elk.algorithm": "layered", "elk.direction": "RIGHT", "elk.spacing.nodeNode": "60"}),
    "force": json.dumps({"elk.algorithm": "org.eclipse.elk.force", "elk.spacing.nodeNode": "80"}),
    "radial": json.dumps({"elk.algorithm": "org.eclipse.elk.radial", "elk.spacing.nodeNode": "80"}),
}

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Animated Layout Transitions", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text("Nodes animate smoothly between positions when switching ELK layouts.", size="sm", c="dimmed", mb="md"),
    dmc.Group([
        dmc.Select(
            id="df-anim-layout-select",
            data=[
                {"value": "layered-down", "label": "Layered (Down)"},
                {"value": "layered-right", "label": "Layered (Right)"},
                {"value": "force", "label": "Force-Directed"},
                {"value": "radial", "label": "Radial"},
            ],
            value="layered-down",
            w=200,
        ),
    ], mb="md"),
    dash_flows.DashFlows(
        id="df-animated-layout",
        nodes=nodes,
        edges=edges,
        animateLayout=True,
        animateLayoutDuration=500,
        style={"height": "400px"},
        fitView=True,
        showControls=True,
        showMiniMap=True,
        layoutOptions=LAYOUTS["layered-down"],
    ),
], p="md", withBorder=True, radius="md")


@callback(
    Output("df-animated-layout", "layoutOptions"),
    Input("df-anim-layout-select", "value"),
    prevent_initial_call=True,
)
def switch_df_layout(layout_key):
    from dash import no_update
    if not layout_key:
        return no_update
    return LAYOUTS.get(layout_key, LAYOUTS["layered-down"])
```


**Computing Flows** — Topological sort and data propagation:



```python
# File: docs/dash_flows/computing_flows.py

"""Computing Flows — topological sort and data propagation."""
import dash_mantine_components as dmc
from dash import callback, Input, Output
from dash_iconify import DashIconify
import dash_flows
import json

nodes = [
    {"id": "input-a", "type": "input", "data": {"label": "Input A", "computedValue": 10}, "position": {"x": 0, "y": 0}},
    {"id": "input-b", "type": "input", "data": {"label": "Input B", "computedValue": 5}, "position": {"x": 0, "y": 150}},
    {"id": "add", "type": "default", "data": {"label": "Add"}, "position": {"x": 250, "y": 75}},
    {"id": "multiply", "type": "default", "data": {"label": "Multiply ×2"}, "position": {"x": 500, "y": 75}},
    {"id": "result", "type": "output", "data": {"label": "Result"}, "position": {"x": 750, "y": 75}},
]
edges = [
    {"id": "e1", "source": "input-a", "target": "add", "animated": True},
    {"id": "e2", "source": "input-b", "target": "add", "animated": True},
    {"id": "e3", "source": "add", "target": "multiply", "animated": True},
    {"id": "e4", "source": "multiply", "target": "result", "animated": True},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Computing Flows", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Topological sort determines execution order. JS handles graph traversal; Python handles computation.",
        size="sm", c="dimmed", mb="md",
    ),
    dmc.Group([
        dmc.Button("Compute Flow", id="df-compute-btn", leftSection=DashIconify(icon="tabler:player-play", width=16), color="green", size="sm"),
    ], mb="md"),
    dash_flows.DashFlows(
        id="df-computing-flow",
        nodes=nodes,
        edges=edges,
        style={"height": "280px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Code(id="df-compute-result", children="Click 'Compute Flow' to run topological sort", block=True),
], p="md", withBorder=True, radius="md")


@callback(
    Output("df-computing-flow", "computeAction"),
    Input("df-compute-btn", "n_clicks"),
    prevent_initial_call=True,
)
def trigger_compute(_n):
    return {"action": "compute"}


@callback(
    Output("df-compute-result", "children"),
    Input("df-computing-flow", "computeResult"),
    prevent_initial_call=True,
)
def show_result(result):
    if not result:
        return "No result yet"
    return json.dumps(result, indent=2)
```


**Resize Constraints** — Aspect ratio lock and min/max dimensions:



```python
# File: docs/dash_flows/resize_constraints.py

"""Resize Constraints — aspect ratio, min/max dimensions."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {
        "id": "free", "type": "resizable",
        "data": {"label": "Free Resize", "sublabel": "No constraints"},
        "position": {"x": 50, "y": 50},
        "style": {"width": 180, "height": 100},
    },
    {
        "id": "aspect", "type": "resizable",
        "data": {"label": "Locked Aspect", "sublabel": "keepAspectRatio: true", "keepAspectRatio": True},
        "position": {"x": 300, "y": 50},
        "style": {"width": 180, "height": 120},
    },
    {
        "id": "constrained", "type": "resizable",
        "data": {
            "label": "Min/Max Limits",
            "sublabel": "100-400px wide, 80-200px tall",
            "minWidth": 100, "minHeight": 80,
            "maxWidth": 400, "maxHeight": 200,
        },
        "position": {"x": 550, "y": 50},
        "style": {"width": 200, "height": 120},
    },
]
edges = [
    {"id": "e1", "source": "free", "target": "aspect"},
    {"id": "e2", "source": "aspect", "target": "constrained"},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Resize Constraints", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text("Drag the resize handles on each node to see different constraint behaviors.", size="sm", c="dimmed", mb="md"),
    dash_flows.DashFlows(
        id="df-resize-constraints",
        nodes=nodes,
        edges=edges,
        style={"height": "300px"},
        fitView=True,
        showControls=True,
    ),
    dmc.SimpleGrid(cols=3, mt="md", children=[
        dmc.Badge("Free: no limits", color="gray", variant="light", fullWidth=True),
        dmc.Badge("Aspect: ratio locked", color="blue", variant="light", fullWidth=True),
        dmc.Badge("Min/Max: bounded size", color="orange", variant="light", fullWidth=True),
    ]),
], p="md", withBorder=True, radius="md")
```


**Accessibility (ARIA)** — Screen reader labels and keyboard navigation:



```python
# File: docs/dash_flows/accessibility.py

"""Accessibility — ARIA labels and keyboard navigation."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {"id": "start", "type": "input", "data": {"label": "Start"}, "position": {"x": 50, "y": 50}, "ariaLabel": "Start node — entry point of the workflow"},
    {"id": "validate", "type": "default", "data": {"label": "Validate"}, "position": {"x": 250, "y": 50}, "ariaLabel": "Validation step — checks input data integrity"},
    {"id": "process", "type": "default", "data": {"label": "Process"}, "position": {"x": 450, "y": 50}, "ariaLabel": "Processing step — transforms validated data"},
    {"id": "end", "type": "output", "data": {"label": "Complete"}, "position": {"x": 650, "y": 50}, "ariaLabel": "Completion node — workflow output"},
]
edges = [
    {"id": "e1", "source": "start", "target": "validate", "ariaLabel": "Flow from start to validation"},
    {"id": "e2", "source": "validate", "target": "process", "ariaLabel": "Flow from validation to processing"},
    {"id": "e3", "source": "process", "target": "end", "ariaLabel": "Flow from processing to completion"},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Accessibility (ARIA Support)", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text("Full ARIA labels for screen readers. Tab through nodes, use arrow keys to pan.", size="sm", c="dimmed", mb="md"),
    dash_flows.DashFlows(
        id="df-accessibility",
        nodes=nodes,
        edges=edges,
        nodesFocusable=True,
        edgesFocusable=True,
        ariaLabelConfig={
            "rfDiagram": "Interactive data processing workflow",
            "miniMap": "Minimap navigation panel",
            "controls": "Zoom and pan controls",
        },
        style={"height": "280px"},
        fitView=True,
        showControls=True,
        showMiniMap=True,
    ),
    dmc.Alert(
        dmc.Stack([
            dmc.Text("Keyboard Navigation:", size="sm", fw=500),
            dmc.List([
                dmc.ListItem("Tab — cycle through nodes and edges"),
                dmc.ListItem("Enter — select focused element"),
                dmc.ListItem("Delete/Backspace — remove selected"),
                dmc.ListItem("Arrow keys — pan the viewport"),
            ], size="sm"),
        ], gap="xs"),
        color="indigo", variant="light", mt="md",
    ),
], p="md", withBorder=True, radius="md")
```


**Viewport Portal** — Floating annotations at flow coordinates:



```python
# File: docs/dash_flows/viewport_portal.py

"""Viewport Portal — floating annotations at flow coordinates."""
import dash_mantine_components as dmc
import dash_flows

nodes = [
    {"id": "api", "type": "input", "data": {"label": "API Layer"}, "position": {"x": 100, "y": 100}},
    {"id": "cache", "type": "default", "data": {"label": "Cache"}, "position": {"x": 350, "y": 50}},
    {"id": "db", "type": "default", "data": {"label": "Database"}, "position": {"x": 350, "y": 180}},
    {"id": "response", "type": "output", "data": {"label": "Response"}, "position": {"x": 600, "y": 100}},
]
edges = [
    {"id": "e1", "source": "api", "target": "cache"},
    {"id": "e2", "source": "api", "target": "db"},
    {"id": "e3", "source": "cache", "target": "response"},
    {"id": "e4", "source": "db", "target": "response"},
]

overlays = [
    {"x": 80, "y": 30, "content": "Incoming Traffic", "style": {"background": "rgba(59,130,246,0.1)", "color": "#3b82f6", "padding": "4px 10px", "borderRadius": "6px", "fontSize": "12px", "fontWeight": "600", "border": "1px solid rgba(59,130,246,0.3)"}},
    {"x": 320, "y": -10, "content": "Hot Path (< 5ms)", "style": {"background": "rgba(34,197,94,0.1)", "color": "#22c55e", "padding": "4px 10px", "borderRadius": "6px", "fontSize": "11px", "border": "1px solid rgba(34,197,94,0.3)"}},
    {"x": 320, "y": 250, "content": "Cold Path (50-200ms)", "style": {"background": "rgba(249,115,22,0.1)", "color": "#f97316", "padding": "4px 10px", "borderRadius": "6px", "fontSize": "11px", "border": "1px solid rgba(249,115,22,0.3)"}},
    {"x": 580, "y": 30, "content": "Output", "style": {"background": "rgba(168,85,247,0.1)", "color": "#a855f7", "padding": "4px 10px", "borderRadius": "6px", "fontSize": "12px", "fontWeight": "600", "border": "1px solid rgba(168,85,247,0.3)"}},
]

component = dmc.Paper([
    dmc.Group([
        dmc.Text("Viewport Portal (Floating Annotations)", fw=600),
        dmc.Badge("1.2.0", color="teal", variant="light", size="sm"),
    ], mb="xs"),
    dmc.Text(
        "Floating overlays anchored to flow coordinates that move with pan and zoom. "
        "Use for annotations, labels, region markers, and performance indicators.",
        size="sm", c="dimmed", mb="md",
    ),
    dash_flows.DashFlows(
        id="df-viewport-portal",
        nodes=nodes,
        edges=edges,
        viewportOverlays=overlays,
        style={"height": "350px"},
        fitView=True,
        showControls=True,
    ),
    dmc.Code(
        '''viewportOverlays=[
    {"x": 100, "y": 50, "content": "Label", "style": {"background": "...", "color": "..."}},
]''',
        block=True,
    ),
], p="md", withBorder=True, radius="md")
```


---

### 1.2.0 Props Reference

| Property | Type | Default | Description |
|:---------|:-----|:--------|:------------|
| `smartHandles` | bool | `False` | Auto-route edges to closest node side |
| `helperLines` | bool | `False` | Show alignment guides when dragging |
| `helperLineThreshold` | number | `5` | Snap distance in pixels |
| `addNodeOnEdgeDrop` | bool | `False` | Create node by dropping edge on canvas |
| `animateLayout` | bool | `False` | Smooth transitions between layouts |
| `animateLayoutDuration` | number | `300` | Animation duration (ms) |
| `enableUndoRedo` | bool | `False` | Enable history tracking |
| `undoRedoMaxHistory` | number | `50` | Max history snapshots |
| `undoRedoAction` | dict | `None` | Trigger undo/redo: `{action: 'undo'}` |
| `undoRedoState` | dict | Output | `{canUndo, canRedo, undoCount, redoCount}` |
| `computeAction` | dict | `None` | Trigger computation: `{action: 'compute'}` |
| `computeResult` | dict | Output | `{traversalOrder, nodeInputs, timestamp}` |
| `toggleCollapseNode` | string | `None` | Group node ID to toggle collapse |
| `collapsedGroups` | list | Output | Currently collapsed group IDs |
| `viewportOverlays` | list | `None` | Floating annotations `[{x, y, content, style}]` |
| `connectionDragThreshold` | number | `1` | Min drag distance before connection |
| `zIndexMode` | string | `"default"` | `"default"` or `"elevate"` |
| `autoPanOnNodeFocus` | bool | `False` | Pan when Tab-focusing nodes |
| `nodesFocusable` | bool | `False` | Enable Tab navigation |
| `edgesFocusable` | bool | `False` | Enable Tab for edges |
| `ariaLabelConfig` | dict | `None` | ARIA labels for regions |
| `deleteElementsAction` | dict | `None` | Programmatic deletion |
| `edgeDroppedNode` | dict | Output | Node created from dropped edge |
| `nodeConnections` | dict | Output | Real-time connection map |

---

### Troubleshooting

#### Nodes Not Appearing

- Ensure each node has `id`, `type`, `data`, and `position`
- Check that container has explicit height: `style={"height": "500px"}`
- Verify node IDs are unique strings

#### Edges Not Connecting

- Verify `source` and `target` match existing node IDs
- Check that source node has a source handle (not output type)
- Check that target node has a target handle (not input type)

#### Callbacks Not Firing

- Add `prevent_initial_call=True` to avoid initial triggers
- Ensure component ID matches callback Input/Output
- Check browser console for JavaScript errors

#### Layout Not Applying

- Ensure `layoutOptions` is a valid JSON string via `json.dumps()`
- Verify ELK algorithm name is correct
- Try adding explicit spacing options

#### Multi-Select Not Working

- Set `multiSelectionKeyCode="Shift"` explicitly
- Ensure `elementsSelectable=True`

#### Dark Mode Issues

- Use modern DMC v2.4.0+ pattern
- Wrap in `dmc.MantineProvider`
- Set `colorMode="dark"` on DashFlows

---

### Resources

- [React Flow Documentation](https://reactflow.dev/) - Underlying library
- [Iconify Icon Search](https://icon-sets.iconify.design/) - Browse icons
- [ELK Layout Options](https://eclipse.dev/elk/reference/options.html) - Layout configuration
- [Dash Mantine Components](https://www.dash-mantine-components.com/) - UI components
