Skip to content
edmi-ui

Plan

Plan card with streaming title and description and collapsible steps.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Planning…
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Planning…
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Rebalance MAG4

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Planning…

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

3. Send one transaction and verify weights

Built on card, button, collapsible and ai-shimmer.

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

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

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-plan.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-plan.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-plan.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-plan.json

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

import {
Plan,
PlanAction,
PlanContent,
PlanDescription,
PlanFooter,
PlanHeader,
PlanTitle,
PlanTrigger,
} from "@/components/ai/plan";
import { Button } from "@/components/ui/button";
<Plan isStreaming={streaming} defaultOpen className="w-full max-w-md">
<PlanHeader>
<div>
<PlanTitle>{streaming ? "Planning…" : "Rebalance MAG4"}</PlanTitle>
<PlanDescription>3 steps · about 1 minute</PlanDescription>
</div>
<PlanAction>
<PlanTrigger />
</PlanAction>
</PlanHeader>
<PlanContent>
</PlanContent>
<PlanFooter>
<Button size="sm">Start</Button>
<Button size="sm" variant="ghost">
Edit plan
</Button>
</PlanFooter>
</Plan>
<script setup lang="ts">
import {
Plan,
PlanAction,
PlanContent,
PlanDescription,
PlanFooter,
PlanHeader,
PlanTitle,
PlanTrigger,
} from "@/components/ai/plan";
import { Button } from "@/components/ui/button";
</script>
<template>
<div class="flex flex-col gap-4">
<Plan v-for="streaming in [false, true]" :key="String(streaming)" :is-streaming="streaming" default-open class="w-full max-w-md">
<PlanHeader>
<div>
<PlanTitle>{{ streaming ? "Planning…" : "Rebalance MAG4" }}</PlanTitle>
<PlanDescription>3 steps · about 1 minute</PlanDescription>
</div>
<PlanAction>
<PlanTrigger />
</PlanAction>
</PlanHeader>
<PlanContent>
<p :key="s">{{ s }}</p>
</PlanContent>
<PlanFooter>
<Button size="sm">Start</Button>
<Button size="sm" variant="ghost">Edit plan</Button>
</PlanFooter>
</Plan>
</div>
</template>
<script lang="ts">
import {
Plan,
PlanAction,
PlanContent,
PlanDescription,
PlanFooter,
PlanHeader,
PlanTitle,
PlanTrigger,
} from "$lib/components/ai/plan";
import { Button } from "$lib/components/ui/button";
</script>
<div class="flex flex-col gap-4">
{#each [false, true] as streaming (streaming)}
<Plan isStreaming={streaming} open class="w-full max-w-md">
<PlanHeader>
<div>
<PlanTitle>{streaming ? "Planning…" : "Rebalance MAG4"}</PlanTitle>
<PlanDescription>3 steps · about 1 minute</PlanDescription>
</div>
<PlanAction>
<PlanTrigger />
</PlanAction>
</PlanHeader>
<PlanContent>
{#each steps as s (s)}
<p>{s}</p>
{/each}
</PlanContent>
<PlanFooter>
<Button size="sm">Start</Button>
<Button size="sm" variant="ghost">Edit plan</Button>
</PlanFooter>
</Plan>
{/each}
</div>
registry/components/ai/plan.tsx
"use client";
import { cn } from "cn";
import type { ComponentProps } from "react";
import { createContext, useContext, useMemo } from "react";
import { CaretUpDownIcon } from "@phosphor-icons/react";
import { Shimmer } from "@/registry/edmi/components/ai/shimmer";
import { Button } from "@/registry/edmi/ui/button";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/edmi/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/edmi/ui/collapsible";
import type { Elevation } from "@/registry/edmi/ui/elevation";
interface PlanContextValue {
isStreaming: boolean;
}
const PlanContext = createContext<PlanContextValue | null>(null);
const usePlan = () => {
const context = useContext(PlanContext);
if (!context) {
throw new Error("Plan components must be used within Plan");
}
return context;
};
export type PlanProps = Omit<ComponentProps<typeof Collapsible>, "render"> & {
isStreaming?: boolean;
/** ✦ depth of the card (sunken -1, flat 0, raised +1, floating +2). */
elevation?: Elevation;
};
export const Plan = ({
className,
isStreaming = false,
elevation,
children,
...props
}: PlanProps) => {
const contextValue = useMemo(() => ({ isStreaming }), [isStreaming]);
return (
<PlanContext.Provider value={contextValue}>
<Collapsible
data-slot="ai-plan"
render={
<Card
className={cn("gap-0 py-0 [--card-spacing:16px]", className)}
elevation={elevation}
/>
}
{...props}
>
{children}
</Collapsible>
</PlanContext.Provider>
);
};
export type PlanHeaderProps = ComponentProps<typeof CardHeader>;
export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
<CardHeader
className={cn("flex items-start justify-between gap-2.5 py-3.5", className)}
data-slot="ai-plan-header"
{...props}
/>
);
export type PlanTitleProps = Omit<
ComponentProps<typeof CardTitle>,
"children"
> & {
children: string;
};
export const PlanTitle = ({
className,
children,
...props
}: PlanTitleProps) => {
const { isStreaming } = usePlan();
return (
<CardTitle
className={cn("text-[15px]", className)}
data-slot="ai-plan-title"
{...props}
>
{isStreaming ? <Shimmer as="span">{children}</Shimmer> : children}
</CardTitle>
);
};
export type PlanDescriptionProps = Omit<
ComponentProps<typeof CardDescription>,
"children"
> & {
children: string;
};
export const PlanDescription = ({
className,
children,
...props
}: PlanDescriptionProps) => {
const { isStreaming } = usePlan();
return (
<CardDescription
className={cn("text-xs text-balance", className)}
data-slot="ai-plan-description"
{...props}
>
{isStreaming ? <Shimmer as="span">{children}</Shimmer> : children}
</CardDescription>
);
};
export type PlanActionProps = ComponentProps<typeof CardAction>;
export const PlanAction = (props: PlanActionProps) => (
<CardAction data-slot="ai-plan-action" {...props} />
);
export type PlanContentProps = ComponentProps<typeof CardContent>;
export const PlanContent = ({ className, ...props }: PlanContentProps) => (
<CollapsibleContent className="h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
<CardContent
className={cn(
"pb-3.5 text-[13.5px] leading-[1.7] text-foreground-2",
className,
)}
data-slot="ai-plan-content"
{...props}
/>
</CollapsibleContent>
);
export type PlanFooterProps = ComponentProps<typeof CardFooter>;
export const PlanFooter = ({ className, ...props }: PlanFooterProps) => (
<CardFooter
className={cn("px-4 py-2.5", className)}
data-slot="ai-plan-footer"
{...props}
/>
);
export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
<CollapsibleTrigger
data-slot="ai-plan-trigger"
render={
<Button
className={cn("size-8", className)}
size="icon-sm"
variant="ghost"
/>
}
{...props}
>
<CaretUpDownIcon
className="size-4"
/>
<span className="sr-only">Toggle plan</span>
</CollapsibleTrigger>
);
registry/components/ai/plan/Plan.vue
<script setup lang="ts">
import type { CollapsibleRootProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { computed, provide } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Card } from "@/registry/edmi/ui/card"
import { Collapsible } from "@/registry/edmi/ui/collapsible"
import type { Elevation } from "@/registry/edmi/ui/elevation"
import { PlanKey } from "./context"
interface PlanProps extends /* @vue-ignore */ CollapsibleRootProps {
class?: HTMLAttributes["class"]
isStreaming?: boolean
/** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */
elevation?: Elevation
}
const props = withDefaults(defineProps<PlanProps>(), {
isStreaming: false,
elevation: undefined,
})
provide(PlanKey, { isStreaming: computed(() => props.isStreaming) })
</script>
<template>
<!-- Collapsible props (open, defaultOpen, update:open) fall through as attrs. -->
<Collapsible as-child data-slot="ai-plan">
<Card
:elevation="props.elevation"
:class="cn('gap-0 py-0 [--card-spacing:16px]', props.class)"
>
<slot />
</Card>
</Collapsible>
</template>
registry/components/ai/plan/PlanAction.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { CardAction } from "@/registry/edmi/ui/card"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<CardAction data-slot="ai-plan-action" :class="props.class">
<slot />
</CardAction>
</template>
registry/components/ai/plan/PlanContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { CardContent } from "@/registry/edmi/ui/card"
import { CollapsibleContent } from "@/registry/edmi/ui/collapsible"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<CollapsibleContent class="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<CardContent
data-slot="ai-plan-content"
:class="cn('pb-3.5 text-[13.5px] leading-[1.7] text-foreground-2', props.class)"
>
<slot />
</CardContent>
</CollapsibleContent>
</template>
registry/components/ai/plan/PlanDescription.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed, useSlots } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Shimmer } from "@/registry/edmi/components/ai/shimmer"
import { CardDescription } from "@/registry/edmi/ui/card"
import { slotText, usePlanContext } from "./context"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
const { isStreaming } = usePlanContext()
const slots = useSlots()
const text = computed(() => slotText(slots.default?.()))
</script>
<template>
<CardDescription data-slot="ai-plan-description" :class="cn('text-xs text-balance', props.class)">
<Shimmer v-if="isStreaming" as="span">
{{ text }}
</Shimmer>
<slot v-else />
</CardDescription>
</template>
registry/components/ai/plan/PlanFooter.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { CardFooter } from "@/registry/edmi/ui/card"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<CardFooter data-slot="ai-plan-footer" :class="cn('px-4 py-2.5', props.class)">
<slot />
</CardFooter>
</template>
registry/components/ai/plan/PlanHeader.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { CardHeader } from "@/registry/edmi/ui/card"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<CardHeader data-slot="ai-plan-header" :class="cn('flex items-start justify-between gap-2.5 py-3.5', props.class)">
<slot />
</CardHeader>
</template>
registry/components/ai/plan/PlanTitle.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed, useSlots } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Shimmer } from "@/registry/edmi/components/ai/shimmer"
import { CardTitle } from "@/registry/edmi/ui/card"
import { slotText, usePlanContext } from "./context"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
const { isStreaming } = usePlanContext()
const slots = useSlots()
const text = computed(() => slotText(slots.default?.()))
</script>
<template>
<CardTitle data-slot="ai-plan-title" :class="cn('text-[15px]', props.class)">
<Shimmer v-if="isStreaming" as="span">
{{ text }}
</Shimmer>
<slot v-else />
</CardTitle>
</template>
registry/components/ai/plan/PlanTrigger.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ChevronsUpDownIcon } from "@lucide/vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Button } from "@/registry/edmi/ui/button"
import { CollapsibleTrigger } from "@/registry/edmi/ui/collapsible"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<CollapsibleTrigger as-child>
<Button
data-slot="ai-plan-trigger"
variant="ghost"
size="icon-sm"
:class="cn('size-8 data-[state=open]:bg-transparent hover:data-[state=open]:bg-accent', props.class)"
>
<slot>
<ChevronsUpDownIcon class="size-4" />
<span class="sr-only">Toggle plan</span>
</slot>
</Button>
</CollapsibleTrigger>
</template>
registry/components/ai/plan/context.ts
import type { InjectionKey, Ref } from "vue";
import { inject } from "vue";
export interface PlanContextValue {
isStreaming: Ref<boolean>;
}
export const PlanKey: InjectionKey<PlanContextValue> = Symbol("PlanContext");
export function usePlanContext() {
const ctx = inject(PlanKey);
if (!ctx) throw new Error("Plan components must be used within <Plan>");
return ctx;
}
/** Plain text of a slot (string children only); the shimmer needs it to size its highlight. */
export function slotText(nodes: unknown): string {
if (!Array.isArray(nodes)) return "";
let text = "";
for (const node of nodes as Array<{ children?: unknown }>) {
if (typeof node.children === "string") text += node.children;
}
return text;
}
registry/components/ai/plan/index.ts
export * from "./context";
export { default as Plan } from "./Plan.vue";
export { default as PlanAction } from "./PlanAction.vue";
export { default as PlanContent } from "./PlanContent.vue";
export { default as PlanDescription } from "./PlanDescription.vue";
export { default as PlanFooter } from "./PlanFooter.vue";
export { default as PlanHeader } from "./PlanHeader.vue";
export { default as PlanTitle } from "./PlanTitle.vue";
export { default as PlanTrigger } from "./PlanTrigger.vue";
src/lib/registry/ai/plan/index.ts
import Root from "./plan.svelte";
import Action from "./plan-action.svelte";
import Content from "./plan-content.svelte";
import Description from "./plan-description.svelte";
import Footer from "./plan-footer.svelte";
import Header from "./plan-header.svelte";
import Title from "./plan-title.svelte";
import Trigger from "./plan-trigger.svelte";
export * from "./use-plan.svelte.js";
export {
Action,
Action as PlanAction,
Content,
Content as PlanContent,
Description,
Description as PlanDescription,
Footer,
Footer as PlanFooter,
Header,
Header as PlanHeader,
Root,
//
Root as Plan,
Title,
Title as PlanTitle,
Trigger,
Trigger as PlanTrigger,
};
src/lib/registry/ai/plan/plan-action.svelte
<script lang="ts">
import { CardAction } from "$lib/registry/ui/card/index.js";
import type { ComponentProps } from "svelte";
let { children, ...restProps }: ComponentProps<typeof CardAction> = $props();
</script>
<CardAction data-slot="ai-plan-action" {...restProps}>
{@render children?.()}
</CardAction>
src/lib/registry/ai/plan/plan-content.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { CardContent } from "$lib/registry/ui/card/index.js";
import { CollapsibleContent } from "$lib/registry/ui/collapsible/index.js";
import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof CardContent> = $props();
</script>
<CollapsibleContent
class="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down"
>
<CardContent
data-slot="ai-plan-content"
class={cn("pb-3.5 text-[13.5px] leading-[1.7] text-foreground-2", className)}
{...restProps}
>
{@render children?.()}
</CardContent>
</CollapsibleContent>
src/lib/registry/ai/plan/plan-description.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { CardDescription } from "$lib/registry/ui/card/index.js";
import type { ComponentProps } from "svelte";
import { Shimmer } from "../shimmer/index.js";
import { usePlan } from "./use-plan.svelte.js";
let { class: className, children, ...restProps }: ComponentProps<typeof CardDescription> = $props();
const plan = usePlan();
</script>
<CardDescription data-slot="ai-plan-description" class={cn("text-xs text-balance", className)} {...restProps}>
{#if plan.isStreaming}
<Shimmer as="span">{@render children?.()}</Shimmer>
{:else}
{@render children?.()}
{/if}
</CardDescription>
src/lib/registry/ai/plan/plan-footer.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { CardFooter } from "$lib/registry/ui/card/index.js";
import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof CardFooter> = $props();
</script>
<CardFooter data-slot="ai-plan-footer" class={cn("px-4 py-2.5", className)} {...restProps}>
{@render children?.()}
</CardFooter>
src/lib/registry/ai/plan/plan-header.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { CardHeader } from "$lib/registry/ui/card/index.js";
import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof CardHeader> = $props();
</script>
<CardHeader
data-slot="ai-plan-header"
class={cn("flex items-start justify-between gap-2.5 py-3.5", className)}
{...restProps}
>
{@render children?.()}
</CardHeader>
src/lib/registry/ai/plan/plan-title.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { CardTitle } from "$lib/registry/ui/card/index.js";
import type { ComponentProps } from "svelte";
import { Shimmer } from "../shimmer/index.js";
import { usePlan } from "./use-plan.svelte.js";
let { class: className, children, ...restProps }: ComponentProps<typeof CardTitle> = $props();
const plan = usePlan();
</script>
<CardTitle data-slot="ai-plan-title" class={cn("text-[15px]", className)} {...restProps}>
{#if plan.isStreaming}
<Shimmer as="span">{@render children?.()}</Shimmer>
{:else}
{@render children?.()}
{/if}
</CardTitle>
src/lib/registry/ai/plan/plan-trigger.svelte
<script lang="ts">
import CaretUpDownIcon from "phosphor-svelte/lib/CaretUpDown";
import { cn } from "$lib/utils.js";
import { Button } from "$lib/registry/ui/button/index.js";
import { CollapsibleTrigger } from "$lib/registry/ui/collapsible/index.js";
import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof CollapsibleTrigger> = $props();
</script>
<CollapsibleTrigger data-slot="ai-plan-trigger" {...restProps}>
{#snippet child({ props })}
<Button
{...props}
variant="ghost"
size="icon-sm"
class={cn("size-8 data-[state=open]:bg-transparent hover:data-[state=open]:bg-accent", className)}
>
{#if children}
{@render children()}
{:else}
<CaretUpDownIcon
class="size-4"
/>
<span class="sr-only">Toggle plan</span>
{/if}
</Button>
{/snippet}
</CollapsibleTrigger>
src/lib/registry/ai/plan/plan.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { Card } from "$lib/registry/ui/card/index.js";
import type { Elevation } from "$lib/registry/ui/elevation/index.js";
import { Collapsible } from "$lib/registry/ui/collapsible/index.js";
import type { ComponentProps } from "svelte";
import { setPlanContext } from "./use-plan.svelte.js";
let {
class: className,
isStreaming = false,
elevation = "auto",
open = $bindable(false),
children,
...restProps
}: Omit<ComponentProps<typeof Collapsible>, "child"> & {
isStreaming?: boolean;
/** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */
elevation?: Elevation;
} = $props();
setPlanContext({
get isStreaming() {
return isStreaming;
},
});
</script>
<Collapsible bind:open data-slot="ai-plan" {...restProps}>
{#snippet child({ props })}
<Card {...props} {elevation} class={cn("gap-0 py-0 [--card-spacing:16px]", className)}>
{@render children?.()}
</Card>
{/snippet}
</Collapsible>
src/lib/registry/ai/plan/use-plan.svelte.ts
import { getContext, setContext } from "svelte";
/** Getter object so consumers stay reactive. */
export interface PlanContextValue {
readonly isStreaming: boolean;
}
const KEY = Symbol("ai-plan");
export function setPlanContext(value: PlanContextValue) {
return setContext(KEY, value);
}
export function usePlan(): PlanContextValue {
const ctx = getContext<PlanContextValue | undefined>(KEY);
if (!ctx) throw new Error("Plan components must be used within <Plan>");
return ctx;
}

See the Elevation guide for the levels, roles and layered mode.

The plan card are flat by default and take the surface level in layered mode (raised). sunken (-1) is a soft inset, raised (+1) a bevel and floating (+2) a bevel with a soft drop.

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Sunken (-1)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Flat (0)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Raised (+1)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Floating (+2)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Sunken (-1)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Flat (0)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Raised (+1)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Floating (+2)

Rebalance MAG4
3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Sunken (-1)

Rebalance MAG4

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Flat (0)

Rebalance MAG4

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Raised (+1)

Rebalance MAG4

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Floating (+2)

Rebalance MAG4

3 steps · about 1 minute

1. Quote all four legs on Jupiter

2. Ask for approval

Part Prop Type Default
Plan isStreaming boolean (title and description shimmer) false
Plan elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"
Plan open, defaultOpen, onOpenChange collapsible state closed
PlanTitle, PlanDescription children string
PlanTrigger Collapsible trigger ghost icon button
PlanFooter card footer
  • elevation ✦ goes to the Card; the footer Buttons take their own elevation.
  • The footer sits on the shell colour, like every Card footer in Edmi.