Skip to content
edmi-ui

Panel

Canvas overlay panel positioned in a corner of the workflow.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Legend

active flow

conditional

Legend

active flow

conditional

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.

Legend

active flow

conditional

Workflow only: it is not the ui inset-panel.

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

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-panel
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ai-panel
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ai-panel
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ai-panel

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-panel.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-panel.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-panel.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-panel.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 { Panel } from "@/components/ai/panel";
import { Button } from "@/components/ui/button";
<div className="h-56 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas edges={[]} nodes={[]}>
<Panel className="px-3 py-2.5 text-xs" position="top-left">
<p className="mb-1.5 font-semibold">Legend</p>
<p className="flex items-center gap-2">
<svg aria-hidden="true" height="6" width="26">
<path
d="M0 3h26"
stroke="var(--brand)"
strokeDasharray="6 5"
strokeWidth="1.6"
/>
</svg>
active flow
</p>
{/* … */}
</Panel>
<Panel className="flex items-center gap-1.5" position="top-right">
<Button size="sm">Run</Button>
<Button size="sm" variant="outline">
Save
</Button>
</Panel>
</Canvas>
</div>
<script setup lang="ts">
import { Canvas } from "@/components/ai/canvas";
import { Panel } from "@/components/ai/panel";
import { Button } from "@/components/ui/button";
</script>
<template>
<div class="h-56 w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas :nodes="[]" :edges="[]">
<Panel position="top-left" class="px-3 py-2.5 text-xs">
<p class="mb-1.5 font-semibold">Legend</p>
<p class="flex items-center gap-2">
<svg aria-hidden="true" height="6" width="26"><path d="M0 3h26" stroke="var(--brand)" stroke-dasharray="6 5" stroke-width="1.6" /></svg>
active flow
</p>
<p class="mt-1 flex items-center gap-2">
<svg aria-hidden="true" height="6" width="26"><path d="M0 3h26" stroke="var(--muted-foreground-2)" stroke-dasharray="2 5" stroke-width="1.6" /></svg>
conditional
</p>
</Panel>
<Panel position="top-right" class="flex items-center gap-1.5">
<Button size="sm">Run</Button>
<Button size="sm" variant="outline">Save</Button>
</Panel>
</Canvas>
</div>
</template>
<script lang="ts">
import { Canvas } from "$lib/components/ai/canvas";
import { Panel } from "$lib/components/ai/panel";
import { Button } from "$lib/components/ui/button";
</script>
<div style="height: 224px" class="w-full overflow-hidden rounded-[calc(var(--radius)*1.4)] border border-border">
<Canvas nodes={[]} edges={[]}>
<Panel position="top-left" class="px-3 py-2.5 text-xs">
<p class="mb-1.5 font-semibold">Legend</p>
<p class="flex items-center gap-2">
<svg aria-hidden="true" height="6" width="26"><path d="M0 3h26" stroke="var(--brand)" stroke-dasharray="6 5" stroke-width="1.6" /></svg>
active flow
</p>
<p class="mt-1 flex items-center gap-2">
<svg aria-hidden="true" height="6" width="26"><path d="M0 3h26" stroke="var(--muted-foreground-2)" stroke-dasharray="2 5" stroke-width="1.6" /></svg>
conditional
</p>
</Panel>
<Panel position="top-right" class="flex items-center gap-1.5">
<Button size="sm">Run</Button>
<Button size="sm" variant="outline">Save</Button>
</Panel>
</Canvas>
</div>
registry/components/ai/panel.tsx
import { Panel as PanelPrimitive } from "@xyflow/react";
import { cn } from "cn";
import type { ComponentProps } from "react";
export type PanelProps = ComponentProps<typeof PanelPrimitive>;
/** Any UI pinned to a canvas corner (legend, filters, run buttons). Workflow only, not the ui inset panel. */
export const Panel = ({ className, ...props }: PanelProps) => (
<PanelPrimitive
data-slot="ai-panel"
className={cn(
"m-4 overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card p-1",
className,
)}
{...props}
/>
);
registry/components/ai/panel/Panel.vue
<script setup lang="ts">
import type { PanelPositionType } from "@vue-flow/core"
import type { HTMLAttributes } from "vue"
import { Panel as PanelPrimitive } from "@vue-flow/core"
import { reactiveOmit } from "@vueuse/core"
import { cn } from "@/registry/edmi/lib/utils"
/** Any UI pinned to a canvas corner (legend, filters, run buttons). Workflow only, not the ui inset panel. */
const props = withDefaults(defineProps<{
class?: HTMLAttributes["class"]
position?: PanelPositionType
}>(), {
position: "top-right",
})
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
<PanelPrimitive
data-slot="ai-panel"
v-bind="delegatedProps"
:class="cn('m-4 overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card p-1', props.class)"
>
<slot />
</PanelPrimitive>
</template>
registry/components/ai/panel/index.ts
export { default as Panel } from "./Panel.vue";
src/lib/registry/ai/panel/panel.svelte
<script lang="ts">
import { Panel as PanelPrimitive, type PanelProps } from "@xyflow/svelte";
import { cn } from "$lib/utils.js";
/** Any UI pinned to a canvas corner (legend, filters, run buttons). Workflow only, not the ui inset panel. */
let { class: className, children, ...restProps }: PanelProps = $props();
</script>
<PanelPrimitive
data-slot="ai-panel"
class={cn(
"m-4 overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card p-1",
className
)}
{...restProps}
>
{@render children?.()}
</PanelPrimitive>
src/lib/registry/ai/panel/index.ts
import Panel from "./panel.svelte";
export { Panel };
Part Prop Type Default
Panel position top-left | top-center | top-right | bottom-left | bottom-center | bottom-right