# ArtisanFlow > An open-source flowchart ecosystem for Alpine.js and Laravel/Livewire. > Complete documentation for AlpineFlow (npm) and WireFlow (Composer). --- # AlpineFlow Documentation # Installation ## npm (recommended) ```bash npm install @getartisanflow/alpineflow alpinejs ``` Register AlpineFlow as an Alpine plugin: ```js import Alpine from 'alpinejs'; import AlpineFlow from '@getartisanflow/alpineflow'; Alpine.plugin(AlpineFlow); Alpine.start(); ``` > **Livewire users:** Alpine is already bundled with Livewire. Don't import Alpine separately — just register the plugin on the `alpine:init` event. See [Livewire integration](#livewire). ### CSS AlpineFlow requires structural CSS. A default theme is optional but recommended: ```css /* Both structural + default theme */ @import '@getartisanflow/alpineflow/css'; @import '@getartisanflow/alpineflow/theme'; ``` Or import structural CSS only and bring your own theme: ```css @import '@getartisanflow/alpineflow/css'; ``` See [Theming](../theming/_index.md) for CSS variable customization. ## CDN For prototyping or non-bundler setups: ```html ``` The CDN build auto-registers with Alpine via the `alpine:init` event. No manual plugin registration needed. ## Livewire If you're using [WireFlow](https://github.com/getartisanflow/wireflow) (the Livewire integration), AlpineFlow is bundled automatically — no npm install required. See the [WireFlow docs](https://github.com/getartisanflow/wireflow) for setup. If you're using AlpineFlow directly in a Livewire app (without WireFlow), register it on the `alpine:init` event since Livewire manages Alpine's lifecycle: ```js import AlpineFlow from '@getartisanflow/alpineflow'; document.addEventListener('alpine:init', () => { window.Alpine.plugin(AlpineFlow); }); ``` > **Do not** call `Alpine.start()` — Livewire handles that. ## Optional Addons AlpineFlow's core is lightweight. Layout engines, collaboration, and whiteboard tools are separate sub-path imports that require their own peer dependencies: | Addon | Import | Peer Dependency | |-------|--------|-----------------| | Dagre layout | `@getartisanflow/alpineflow/dagre` | `@dagrejs/dagre` | | Force layout | `@getartisanflow/alpineflow/force` | `d3-force` | | Tree layout | `@getartisanflow/alpineflow/hierarchy` | `d3-hierarchy` | | ELK layout | `@getartisanflow/alpineflow/elk` | `elkjs` | | Whiteboard | `@getartisanflow/alpineflow/whiteboard` | — | | Schema designer | `@getartisanflow/alpineflow/schema` | — | | Collaboration | `@getartisanflow/alpineflow/collab` | `yjs`, `y-websocket`, `y-protocols` | Example — adding dagre layout: ```bash npm install @dagrejs/dagre ``` ```js import AlpineFlow from '@getartisanflow/alpineflow'; import AlpineFlowDagre from '@getartisanflow/alpineflow/dagre'; Alpine.plugin(AlpineFlow); Alpine.plugin(AlpineFlowDagre); ``` See [Addons](../addons/_index.md) for detailed setup per addon. ## Requirements - Alpine.js 3.15+ - Modern browser (ES2020+) # First Flow This tutorial walks you through building your first flow diagram, starting with the bare minimum and progressively layering on features. ## Minimal example After [installing](installation.md) AlpineFlow, this is all you need to render an interactive flow: ```html
``` This gives you: - Two draggable nodes connected by an edge - Pan and zoom (mouse drag + scroll wheel) - Connection handles for creating new edges (drag from a handle) ::demo ```html
``` ::enddemo ### What's happening | Element | Purpose | |---------|---------| | `flowCanvas({...})` | Alpine data component — manages all state (nodes, edges, viewport) | | `.flow-container` | CSS class that sets up the canvas dimensions and overflow | | `x-flow-viewport` | The pannable/zoomable viewport layer | | `x-flow-node="node"` | Renders a node and makes it draggable | | `x-flow-handle:target` | Connection handle that accepts incoming edges | | `x-flow-handle:source` | Connection handle that starts outgoing edges | Edges are rendered automatically from the `edges` array — you don't write edge markup. ## Adding features Enable common features by passing config options to `flowCanvas()`: ```html
``` Each option is independent — use only what you need. See [Configuration](../configuration/index.md) for the full list. ::demo ```html
``` ::enddemo ## Custom node content Nodes can contain any HTML. Use Alpine directives for dynamic content: ```html

``` - `x-flow-drag-handle` restricts dragging to the title bar instead of the entire node - Handle position modifiers (`.left`, `.right`, `.top`, `.bottom`) control placement See [Nodes](../nodes/basics.md) and [Handles](../handles/_index.md) for full details. ::demo ```html
``` ::enddemo ## Styling All visual properties are controlled via CSS variables. Override them on `.flow-container`: ```css .flow-container { --flow-node-bg: #1e293b; --flow-node-border: 1px solid #334155; --flow-node-color: #e2e8f0; --flow-edge-stroke: #475569; } ``` See [Theming](../theming/_index.md) for the complete variable reference. ## Next steps - [Core Concepts](concepts.md) — how directives, viewport, and reactive data work - [Configuration](../configuration/index.md) — all `flowCanvas()` options - [Directives](../nodes/basics.md) — node, handle, toolbar, and more - [Edge Types](../edges/types.md) — straight, smoothstep, bezier, and custom edges - [Connections](../connections/_index.md) — drag-connect, validation, click-to-connect - [$flow Magic](../api/flow-magic/index.md) — programmatic control via `$flow` # Core Concepts AlpineFlow builds on Alpine.js to turn declarative HTML into interactive flow diagrams. Understanding these six concepts will help you work with every part of the library. ## The flow container Every flow starts with a container element that has two things: the `flow-container` CSS class and an `x-data="flowCanvas({...})"` attribute. ```html
``` Both are required for different reasons: - **`x-data="flowCanvas({...})"`** registers the Alpine data component that manages all flow state — nodes, edges, viewport position, selection, history, and more. This is where you pass your initial configuration. - **`class="flow-container"`** applies the structural CSS that sets up positioning, overflow clipping, and the coordinate system the viewport operates within. Without it, nodes won't position correctly and pan/zoom won't work. The container must have an explicit height (via `style`, a CSS class, or a parent layout) since flow diagrams don't have intrinsic dimensions. ## Directives AlpineFlow extends Alpine with `x-flow-*` directives that attach flow behavior to HTML elements. The naming convention follows a consistent pattern: - **`x-flow-{feature}`** — the base directive, e.g. `x-flow-viewport`, `x-flow-node`, `x-flow-handle` - **Arguments via colon** — pass a role or type after a colon, e.g. `x-flow-handle:source` (a source handle) or `x-flow-handle:target` (a target handle) - **Modifiers via dot** — append behavior modifiers with dots, e.g. `x-flow-handle:source.right` (a source handle positioned on the right edge) or `x-flow-collapse.group` (collapse with group semantics) This mirrors Alpine's own syntax (`x-on:click.prevent`, `x-bind:class`), so if you know Alpine, the pattern is familiar. Some commonly used directives: | Directive | Purpose | |-----------|---------| | `x-flow-viewport` | Wraps the pannable/zoomable layer | | `x-flow-node="node"` | Makes an element a positioned, draggable node | | `x-flow-handle:source` | Adds a connection handle for outgoing edges | | `x-flow-handle:target` | Adds a connection handle for incoming edges | | `x-flow-drag-handle` | Restricts node dragging to a specific child element | | `x-flow-action:fitView` | Binds a button click to a flow action | See [Nodes](../nodes/basics.md) for the full reference. ## The viewport The `x-flow-viewport` directive creates the pannable and zoomable layer. All nodes must be placed inside it: ```html
``` The viewport translates mouse drags into panning and scroll wheel events into zooming. It applies a CSS transform to move and scale all child content together. Elements placed outside the viewport (like toolbars or overlays) stay fixed relative to the container. Edges are **not** placed inside the viewport manually. AlpineFlow renders an SVG edge layer automatically based on the `edges` array — you never write edge markup. ## Reactive data The `nodes` and `edges` arrays you pass to `flowCanvas()` become reactive Alpine data. Mutating them updates the UI immediately: ```js // Adding a node at runtime $flow.addNodes({ id: 'c', position: { x: 100, y: 200 }, data: { label: 'New' } }); // Removing an edge edges = edges.filter(e => e.id !== 'e1'); // Updating a node's data nodes.find(n => n.id === 'a').data.label = 'Updated'; ``` This works because Alpine's reactivity system tracks property access and triggers re-renders when values change. There is no separate "setState" or "dispatch" step — direct mutation is the intended pattern. Key points: - **Nodes** require `id`, `position: { x, y }`, and `data` (an object for your custom properties) - **Edges** require `id`, `source` (node ID), and `target` (node ID) - Edges are rendered automatically from the array — no edge templates or markup needed - Adding, removing, or modifying items in either array triggers a UI update For bulk additions or batched state changes, wrap them in [`$flow.batch(fn)`](../api/flow-magic/nodes.md#batch) so AlpineFlow runs a single reconciliation after your callback instead of one per mutation. ## The $flow magic AlpineFlow registers a `$flow` magic property (available via Alpine's magic system) that gives you programmatic access to the canvas from any expression inside the flow container: ```html ``` Common `$flow` methods include: | Method | Description | |--------|-------------| | `fitView(options?)` | Pan and zoom to fit all (or selected) nodes in view | | `addNodes(node \| nodes[])` | Add one or more nodes to the canvas | | `removeNodes(ids[])` | Remove nodes (and their connected edges) | | `setViewport(viewport, options?)` | Set pan and zoom level | | `animate(targets, options?)` | Smoothly transition node, edge, or viewport properties | | `getNode(id)` | Retrieve a node by ID | | `toObject()` | Export the full flow state as a serializable object | See [$flow Magic](../api/flow-magic/index.md) for the complete API. ## Scope rules Alpine evaluates attributes in the scope of the nearest `x-data` ancestor. This creates an important subtlety: **directives placed on the same element as `x-data` evaluate in that element's own scope, not a parent's.** This means that on the `flowCanvas` element itself, you cannot reference variables from a parent `x-data`: ```html
...
``` If you need parent data accessible inside the flow container (common in WireFlow/Livewire setups), use `Object.assign($data, ...)` to merge it into the flow's scope, or restructure so the parent data lives on the same element: ```html
...
``` This is particularly relevant when using WireFlow, where Livewire's `wire:model` bindings need to coexist with the `flowCanvas` data scope. ::demo ```html
``` ::enddemo # Node Basics Nodes are the primary building blocks of an AlpineFlow diagram. The `x-flow-node` directive binds a DOM element as a flow node, handling positioning, dragging, selection, and all visual state. Drag the nodes to reposition them — edges follow automatically: ::demo ```html
``` ::enddemo ## Usage Apply `x-flow-node` to any element inside a flow canvas. The expression must evaluate to a [FlowNode](#node-data-shape) object: ```html ``` ## What it does - Positions the element absolutely using `node.position` - Makes the node draggable via pointer events (d3-drag) - Selects on click (Shift+click for multi-select — on touch devices, two-finger tap enters selection mode — see [Touch & Mobile](../interaction/touch.md)) - Applies CSS classes reactively: `.flow-node`, `.flow-node-selected`, `.flow-node-locked`, `.flow-node-dragging` (during drag), `.flow-node-{runState}` (when `runState` is set), custom `node.class` - Applies `data-flow-node-type` attribute reflecting `node.type` (or `'default'`), useful for CSS selectors like `[data-flow-node-type="condition"]` - Applies inline styles from `node.style` - Applies dimensions from `node.dimensions` — inline `style.height` is only set when the node is a container (has `childLayout`), is a parent of other nodes (some other node references it via `parentId`), or has `fixedDimensions: true`; plain leaf nodes let content determine height and the ResizeObserver captures the natural height back into `node.dimensions` - Respects per-node flags: `draggable`, `selectable`, `deletable`, `connectable`, `locked`, `hidden` ## Node data shape Every node is a plain object with the following properties: ```js { id: 'node-1', // Required. Unique string ID. position: { x: 100, y: 200 }, // Required. Flow-space coordinates. data: { label: 'My Node' }, // Optional. Arbitrary data for templates. type: 'default', // Optional. Maps to nodeTypes registry. class: 'my-class', // Optional. CSS class(es) added to the node element. style: 'background: red', // Optional. Inline styles or style object. dimensions: { width: 200, height: 80 }, // Optional. Explicit dimensions. fixedDimensions: false, // Optional. Opt-in to inline style.height; ResizeObserver skips node. resizeObserver: true, // Optional. false excludes node from the shared ResizeObserver. minDimensions: { width: 100 }, // Optional. Lower bound applied by observer (Partial). maxDimensions: { width: 800 }, // Optional. Upper bound applied by observer (Partial). selected: false, // Optional. Selection state. draggable: true, // Optional. Per-node drag override. selectable: true, // Optional. Per-node selection override. connectable: true, // Optional. Per-node connection override. deletable: true, // Optional. Per-node delete override. hidden: false, // Optional. Hide from rendering. locked: false, // Optional. Fully freeze all interactions. resizable: true, // Optional. Per-node resize override (requires x-flow-resizer). parentId: 'group-1', // Optional. Makes this a child of another node. expandParent: false, // Optional. Grow parent when child reaches edge. zIndex: 0, // Optional. Explicit z-index. sourcePosition: 'bottom', // Optional. Default handle position for sources. targetPosition: 'top', // Optional. Default handle position for targets. shape: 'diamond', // Optional. Node shape variant. rotation: 0, // Optional. Rotation angle in degrees. nodeOrigin: [0, 0], // Optional. Per-node anchor point override. } ``` Only `id` and `position` are required. Everything else has sensible defaults. ## Per-node flags Override global behavior on individual nodes by setting these flags: | Flag | Type | Default | Description | |------|------|---------|-------------| | `draggable` | `boolean` | `true` | Can be dragged | | `selectable` | `boolean` | `true` | Can be selected | | `connectable` | `boolean` | `true` | Handles accept connections | | `deletable` | `boolean` | `true` | Can be deleted via keyboard (desktop only — provide a delete button for touch users) | | `locked` | `boolean` | `false` | Fully freeze -- no drag, delete, connect, select, or resize. Shows dashed border. Individual flags override when set explicitly. | | `hidden` | `boolean` | `false` | Hidden from rendering | | `resizable` | `boolean` | -- | Per-node resize override (requires `x-flow-resizer`) | > **Note:** Setting `locked: true` freezes all interactions at once. If you need to lock a node but still allow selection, set `locked: true` and `selectable: true` -- explicit flags take precedence over the lock. Try interacting with each node — the locked node has a dashed border, the non-draggable node can't be moved: ::demo ```html
``` ::enddemo ## CSS classes | Class | Applied when | |-------|-------------| | `.flow-node` | Always | | `.flow-node-selected` | Node is selected | | `.flow-node-locked` | `node.locked` is true | | `.flow-node-group` | `node.type` is `'group'` | | `.flow-node-hidden` | `node.hidden` is true | | `.flow-node-{shape}` | Node has a shape (e.g., `.flow-node-diamond`) | ## Custom node content Nodes can contain any HTML. Use the `nodrag` CSS class on interactive elements (buttons, inputs, sliders) to prevent them from triggering a node drag: ```html

``` ::demo ```html
``` ::enddemo ## Node types Register custom templates per node type using `nodeTypes` in your canvas configuration. You can reference a `