Skip to content
edmi-ui

Edge

Animated and temporary edges for the workflow canvas.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

Register as edgeTypes={{ animated: Edge.Animated, temporary: Edge.Temporary }}. Edges without a type use the default solid bezier (--muted-foreground).

Terminal window
npx shadcn@latest add @edmi-ui/ai-edge
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/ai-edge
Terminal window
yarn dlx shadcn@latest add @edmi-ui/ai-edge
Terminal window
bunx shadcn@latest add @edmi-ui/ai-edge

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/ai-edge
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ai-edge
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ai-edge
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ai-edge

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-edge.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import { Canvas } from "@/components/ai/canvas";
import { Edge } from "@/components/ai/edge";
<div className="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas
edgeTypes={{ animated: Edge.Animated, temporary: Edge.Temporary }}
/>
</div>
<script setup lang="ts">
import { Canvas } from "@/components/ai/canvas";
import { Node, NodeHeader, NodeTitle } from "@/components/ai/node";
</script>
<template>
<div class="h-72 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas>
<template #node-step="{ data }">
<Node :handles="{ target: data.t, source: !data.t }" class="w-40">
<NodeHeader class="border-b-0">
<NodeTitle>{{ data.l }}</NodeTitle>
</NodeHeader>
</Node>
</template>
</Canvas>
</div>
</template>
<script lang="ts">
import { Canvas } from "$lib/components/ai/canvas";
</script>
<div style="height: 288px" class="w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas bind:nodes bind:edges />
</div>
registry/components/ai/edge.tsx
import type { EdgeProps } from "@xyflow/react";
import { BaseEdge, getBezierPath } from "@xyflow/react";
/** Conditional or error path: dotted, muted. */
const Temporary = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
}: EdgeProps) => {
const [edgePath] = getBezierPath({
sourcePosition,
sourceX,
sourceY,
targetPosition,
targetX,
targetY,
});
return (
<BaseEdge
id={id}
path={edgePath}
style={{
stroke: "var(--muted-foreground-2)",
strokeDasharray: "2 5",
strokeLinecap: "round",
}}
/>
);
};
/** Active data flow: brand dashes that move toward the target. */
const Animated = ({
id,
markerEnd,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style,
}: EdgeProps) => {
const [edgePath] = getBezierPath({
sourcePosition,
sourceX,
sourceY,
targetPosition,
targetX,
targetY,
});
return (
<BaseEdge
id={id}
markerEnd={markerEnd}
path={edgePath}
style={{
stroke: "var(--brand)",
strokeDasharray: "6 5",
strokeLinecap: "round",
animation: "dashdraw 0.6s linear infinite",
...style,
}}
/>
);
};
export const Edge = {
Animated,
Temporary,
};
registry/components/ai/edge/Animated.vue
<script setup lang="ts">
import type { EdgeProps } from "@vue-flow/core"
import { BaseEdge, getBezierPath } from "@vue-flow/core"
import { computed } from "vue"
/** Active data flow: brand dashes that move toward the target. */
const props = defineProps<EdgeProps>()
const path = computed(() => getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
})[0])
</script>
<template>
<BaseEdge
:id="props.id"
:marker-end="props.markerEnd"
:path="path"
:style="{
stroke: 'var(--brand)',
strokeDasharray: '6 5',
strokeLinecap: 'round',
animation: 'dashdraw 0.6s linear infinite',
...(props.style as Record<string, string>),
}"
/>
</template>
registry/components/ai/edge/Temporary.vue
<script setup lang="ts">
import type { EdgeProps } from "@vue-flow/core"
import { BaseEdge, getBezierPath } from "@vue-flow/core"
import { computed } from "vue"
/** Conditional or error path: dotted, muted. */
const props = defineProps<EdgeProps>()
const path = computed(() => getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
})[0])
</script>
<template>
<BaseEdge
:id="props.id"
:path="path"
:style="{
stroke: 'var(--muted-foreground-2)',
strokeDasharray: '2 5',
strokeLinecap: 'round',
}"
/>
</template>
registry/components/ai/edge/index.ts
import Animated from "./Animated.vue";
import Temporary from "./Temporary.vue";
export { Animated, Temporary };
/** Same shape as the React port: `edgeTypes = { animated: Edge.Animated, temporary: Edge.Temporary }` (wrap in `markRaw`). */
export const Edge = { Animated, Temporary };
src/lib/registry/ai/edge/animated.svelte
<script lang="ts">
import { BaseEdge, type EdgeProps, getBezierPath } from "@xyflow/svelte";
/** Active data flow: brand dashes that move toward the target. */
let {
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
markerEnd,
}: EdgeProps = $props();
const path = $derived(
getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })[0]
);
</script>
<BaseEdge
{id}
{path}
{markerEnd}
style="stroke: var(--brand); stroke-dasharray: 6 5; stroke-linecap: round; animation: dashdraw 0.6s linear infinite;"
/>
src/lib/registry/ai/edge/temporary.svelte
<script lang="ts">
import { BaseEdge, type EdgeProps, getBezierPath } from "@xyflow/svelte";
/** Conditional or error path: dotted, muted. */
let {
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
}: EdgeProps = $props();
const path = $derived(
getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })[0]
);
</script>
<BaseEdge
{id}
{path}
style="stroke: var(--muted-foreground-2); stroke-dasharray: 2 5; stroke-linecap: round;"
/>
src/lib/registry/ai/edge/index.ts
import Animated from "./animated.svelte";
import Temporary from "./temporary.svelte";
export { Animated, Temporary };
/** Same shape as the React port: `edgeTypes = { animated: Edge.Animated, temporary: Edge.Temporary }`. */
export const Edge = { Animated, Temporary };
Part Prop Type Default
Edge.Animated edge type brand dashes that move to the target
Edge.Temporary edge type dotted muted path for conditional or error flow