Skip to content
edmi-ui

Artifact Stack

A group of artifact cards with a Download all action.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Rebalance report
Document · PDF
Rebalance report
Document · MD
Rebalance report
Document · PDF
Rebalance report
Document · MD
Rebalance report
Document · PDF
Rebalance report
Document · MD

Edmi ✦ addition. Built on button and ai-artifact-card.

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

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

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

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

import {
ArtifactCard,
ArtifactCardActions,
ArtifactCardBody,
ArtifactCardIcon,
ArtifactCardMeta,
ArtifactCardThumbnail,
ArtifactCardTitle,
} from "@/components/ai/artifact-card";
import {
ArtifactStack,
ArtifactStackDownloadAll,
} from "@/components/ai/artifact-stack";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
<ArtifactStack className="max-w-lg">
<ArtifactCard>
<ArtifactCardThumbnail />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · PDF</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactCard>
<ArtifactCardIcon kind="document" />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · MD</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactStackDownloadAll />
</ArtifactStack>
<script setup lang="ts">
import {
ArtifactCard,
ArtifactCardActions,
ArtifactCardBody,
ArtifactCardIcon,
ArtifactCardMeta,
ArtifactCardThumbnail,
ArtifactCardTitle,
} from "@/components/ai/artifact-card";
import {
ArtifactStack,
ArtifactStackDownloadAll,
} from "@/components/ai/artifact-stack";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
</script>
<template>
<ArtifactStack class="max-w-lg">
<ArtifactCard>
<ArtifactCardThumbnail />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · PDF</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactCard>
<ArtifactCardIcon kind="document" />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · MD</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactStackDownloadAll />
</ArtifactStack>
</template>
<script lang="ts">
import {
ArtifactCard,
ArtifactCardActions,
ArtifactCardBody,
ArtifactCardIcon,
ArtifactCardMeta,
ArtifactCardThumbnail,
ArtifactCardTitle,
} from "$lib/components/ai/artifact-card";
import {
ArtifactStack,
ArtifactStackDownloadAll,
} from "$lib/components/ai/artifact-stack";
import { DropdownMenuItem } from "$lib/components/ui/dropdown-menu";
</script>
<ArtifactStack class="max-w-lg">
<ArtifactCard>
<ArtifactCardThumbnail />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · PDF</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactCard>
<ArtifactCardIcon kind="document" />
<ArtifactCardBody>
<ArtifactCardTitle>Rebalance report</ArtifactCardTitle>
<ArtifactCardMeta>Document · MD</ArtifactCardMeta>
</ArtifactCardBody>
<ArtifactCardActions>
<DropdownMenuItem>Copy link</DropdownMenuItem>
</ArtifactCardActions>
</ArtifactCard>
<ArtifactStackDownloadAll />
</ArtifactStack>
registry/components/ai/artifact-stack.tsx
import { cn } from "cn";
import type { ComponentProps } from "react";
import { DownloadIcon } from "@phosphor-icons/react";
import { Button } from "@/registry/edmi/ui/button";
export type ArtifactStackProps = ComponentProps<"div">;
/** Several outputs from one answer: artifact cards with a Download all action. */
export const ArtifactStack = ({ className, ...props }: ArtifactStackProps) => (
<div
data-slot="ai-artifact-stack"
className={cn("flex w-full flex-col items-start gap-2", className)}
{...props}
/>
);
export type ArtifactStackDownloadAllProps = ComponentProps<typeof Button>;
export const ArtifactStackDownloadAll = ({
className,
children = "Download all",
...props
}: ArtifactStackDownloadAllProps) => (
<Button
data-slot="ai-artifact-stack-download-all"
variant="secondary"
size="sm"
type="button"
className={cn(className)}
{...props}
>
<DownloadIcon
className="size-3.5"
/>
{children}
</Button>
);
registry/components/ai/artifact-stack/ArtifactStack.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div data-slot="ai-artifact-stack" :class="cn('flex w-full flex-col items-start gap-2', props.class)">
<slot />
</div>
</template>
registry/components/ai/artifact-stack/ArtifactStackDownloadAll.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DownloadIcon } from "@lucide/vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Button } from "@/registry/edmi/ui/button"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<Button
data-slot="ai-artifact-stack-download-all"
variant="secondary"
size="sm"
type="button"
:class="cn(props.class)"
>
<DownloadIcon class="size-3.5" />
<slot>Download all</slot>
</Button>
</template>
registry/components/ai/artifact-stack/index.ts
export { default as ArtifactStack } from "./ArtifactStack.vue";
export { default as ArtifactStackDownloadAll } from "./ArtifactStackDownloadAll.vue";
src/lib/registry/ai/artifact-stack/artifact-stack-download-all.svelte
<script lang="ts">
import DownloadIcon from "phosphor-svelte/lib/Download";
import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js";
let { ref = $bindable(null), class: className, children, ...restProps }: Omit<ButtonProps, "href"> = $props();
</script>
<Button
bind:ref
data-slot="ai-artifact-stack-download-all"
variant="secondary"
size="sm"
type="button"
class={className}
{...restProps}
>
<DownloadIcon
class="size-3.5"
/>
{#if children}
{@render children()}
{:else}
Download all
{/if}
</Button>
src/lib/registry/ai/artifact-stack/artifact-stack.svelte
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>, HTMLDivElement> = $props();
</script>
<div
bind:this={ref}
data-slot="ai-artifact-stack"
class={cn("flex w-full flex-col items-start gap-2", className)}
{...restProps}
>
{@render children?.()}
</div>
src/lib/registry/ai/artifact-stack/index.ts
import ArtifactStack from "./artifact-stack.svelte";
import ArtifactStackDownloadAll from "./artifact-stack-download-all.svelte";
export { ArtifactStack, ArtifactStackDownloadAll };
Part Prop Type Default
ArtifactStack div props
ArtifactStackDownloadAll children, Button props ReactNode (Vue: default slot, Svelte: children snippet) Download all

Flat only. The stack is a layout wrapper; each ArtifactCard inside takes its own elevation.

  • Documents show a paper thumbnail and other files an icon tile; the meta line reads kind · format.