Artifact
Container for generated output with a header, actions and scrollable content.
import { Artifact, ArtifactAction, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";import { CodeBlock } from "@/components/ai/code-block";import { Button } from "@/components/ui/button";import { useState } from "react";import { IconPlaceholder } from "@/edmi/icon-placeholder";
const code = `import { rebalance } from "@/lib/keeper"
export async function run(index: string) { // only when drift is above the limit const drift = await getDrift(index) if (drift < 0.02) return return rebalance(index, { slippage: 0.01 })}`;
export default function Demo() { const [open, setOpen] = useState(true); const [copied, setCopied] = useState(false); const copy = () => { navigator.clipboard?.writeText(code).catch(() => {}); setCopied(true); setTimeout(() => setCopied(false), 1500); }; if (!open) return ( <Button variant="outline" size="sm" onClick={() => setOpen(true)}> Reopen rebalance.ts </Button> ); return ( <Artifact className="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactAction onClick={copy} icon={ copied ? ( <IconPlaceholder lucide="CheckIcon" tabler="IconCheck" hugeicons="Tick02Icon" phosphor="CheckIcon" remixicon="RiCheckLine" className="size-4" /> ) : ( <IconPlaceholder lucide="CopyIcon" tabler="IconCopy" hugeicons="Copy01Icon" phosphor="CopyIcon" remixicon="RiFileCopyLine" className="size-4" /> ) } tooltip="Copy" /> <ArtifactAction icon={ <IconPlaceholder lucide="DownloadIcon" tabler="IconDownload" hugeicons="DownloadIcon" phosphor="DownloadIcon" remixicon="RiDownloadLine" className="size-4" /> } tooltip="Download" /> <ArtifactAction icon={ <IconPlaceholder lucide="ExternalLinkIcon" tabler="IconExternalLink" hugeicons="LinkSquare02Icon" phosphor="ArrowSquareOutIcon" remixicon="RiExternalLinkLine" className="size-4" /> } tooltip="Open" /> <ArtifactClose onClick={() => setOpen(false)} /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <CodeBlock className="rounded-none border-0" code={code} language="typescript" showLineNumbers /> </ArtifactContent> </Artifact> );}<script setup lang="ts">import { CopyIcon, CheckIcon, DownloadIcon, ArrowUpRightIcon } from "@lucide/vue";import { ref } from "vue";import { Button } from "@/components/ui/button";import { Artifact, ArtifactAction, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";import { CodeBlock } from "@/components/ai/code-block";
const code = `import { rebalance } from "@/lib/keeper"
export async function run(index: string) { // only when drift is above the limit const drift = await getDrift(index) if (drift < 0.02) return return rebalance(index, { slippage: 0.01 })}`;
const open = ref(true);const copied = ref(false);function copy() { navigator.clipboard?.writeText(code).catch(() => {}); copied.value = true; setTimeout(() => (copied.value = false), 1500);}</script>
<template> <Button v-if="!open" variant="outline" size="sm" @click="open = true">Reopen rebalance.ts</Button> <Artifact v-else class="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactAction tooltip="Copy" @click="copy"> <CheckIcon v-if="copied" class="size-4" /> <CopyIcon v-else class="size-4" /> </ArtifactAction> <ArtifactAction tooltip="Download"><DownloadIcon class="size-4" /></ArtifactAction> <ArtifactAction tooltip="Open"><ArrowUpRightIcon class="size-4" /></ArtifactAction> <ArtifactClose @click="open = false" /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <CodeBlock class="rounded-none border-0" :code="code" language="typescript" show-line-numbers /> </ArtifactContent> </Artifact></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import { Artifact, ArtifactAction, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle, } from "$lib/components/ai/artifact"; import { CodeBlock } from "$lib/components/ai/code-block"; import { Button } from "$lib/components/ui/button";
const code = `import { rebalance } from "@/lib/keeper"
export async function run(index: string) { // only when drift is above the limit const drift = await getDrift(index) if (drift < 0.02) return return rebalance(index, { slippage: 0.01 })}`;
let open = $state(true); let copied = $state(false); function copy() { navigator.clipboard?.writeText(code).catch(() => {}); copied = true; setTimeout(() => (copied = false), 1500); }</script>
{#if !open} <Button variant="outline" size="sm" onclick={() => (open = true)}>Reopen rebalance.ts</Button>{:else}<Artifact class="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactAction tooltip="Copy" onclick={copy}> {#if copied} <IconPlaceholder lucide="CheckIcon" tabler="IconCheck" hugeicons="Tick02Icon" phosphor="CheckIcon" remixicon="RiCheckLine" class="size-4" /> {:else} <IconPlaceholder lucide="CopyIcon" tabler="IconCopy" hugeicons="Copy01Icon" phosphor="CopyIcon" remixicon="RiFileCopyLine" class="size-4" /> {/if} </ArtifactAction> <ArtifactAction tooltip="Download"> <IconPlaceholder lucide="DownloadIcon" tabler="IconDownload" hugeicons="DownloadIcon" phosphor="DownloadIcon" remixicon="RiDownloadLine" class="size-4" /> </ArtifactAction> <ArtifactAction tooltip="Open"> <IconPlaceholder lucide="ExternalLinkIcon" tabler="IconExternalLink" hugeicons="LinkSquare02Icon" phosphor="ArrowSquareOutIcon" remixicon="RiExternalLinkLine" class="size-4" /> </ArtifactAction> <ArtifactClose onclick={() => (open = false)} /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <CodeBlock class="rounded-none border-0" {code} language="typescript" showLineNumbers /> </ArtifactContent></Artifact>{/if}Built on card, button and tooltip.
Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ai-artifactpnpm dlx shadcn@latest add @edmi-ui/ai-artifactyarn dlx shadcn@latest add @edmi-ui/ai-artifactbunx shadcn@latest add @edmi-ui/ai-artifactPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
button,card,tooltip,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ai-artifactpnpm dlx shadcn-vue@latest add @edmi-ui/ai-artifactyarn dlx shadcn-vue@latest add @edmi-ui/ai-artifactbunx shadcn-vue@latest add @edmi-ui/ai-artifactPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @lucide/vueTerminal window pnpm add @lucide/vueTerminal window yarn add @lucide/vueTerminal window bun add @lucide/vueAdd the Edmi components this one builds on:
button,card,tooltip,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-artifact.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-artifact.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-artifact.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-artifact.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
button,card,tooltip,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Artifact, ArtifactAction, ArtifactActions, ArtifactClose, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";
<Artifact className="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactAction tooltip="Copy" /> <ArtifactAction icon={ <DownloadIcon className="size-4" /> } tooltip="Download" /> <ArtifactAction icon={ <ArrowSquareOutIcon className="size-4" /> } tooltip="Open" /> <ArtifactClose /> </ArtifactActions> </ArtifactHeader> {/* … */}</Artifact><script setup lang="ts">import { Button } from "@/components/ui/button";import { Artifact, ArtifactAction, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";import { CodeBlock } from "@/components/ai/code-block";</script>
<template> <Button variant="outline" size="sm">Reopen rebalance.ts</Button> <Artifact v-else class="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactAction tooltip="Copy" @click="copy"> <CheckIcon class="size-4" /> <CopyIcon v-else class="size-4" /> </ArtifactAction> <ArtifactAction tooltip="Download"><DownloadIcon class="size-4" /></ArtifactAction> <ArtifactAction tooltip="Open"><ArrowUpRightIcon class="size-4" /></ArtifactAction> <ArtifactClose /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <CodeBlock class="rounded-none border-0" language="typescript" show-line-numbers /> </ArtifactContent> </Artifact></template><script lang="ts"> import { Button } from "$lib/components/ui/button";</script>
{#if !open} <Button variant="outline" size="sm">Reopen rebalance.ts</Button>{:else}<!-- … -->Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type { ComponentProps, HTMLAttributes, ReactNode } from "react";import { XIcon } from "@phosphor-icons/react";import { Button } from "@/registry/edmi/ui/button";import { Card } from "@/registry/edmi/ui/card";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/registry/edmi/ui/tooltip";
export type ArtifactProps = ComponentProps<typeof Card>;
/** Container for generated output. Built on the ui card; `elevation` ✦ is forwarded to the card. */export const Artifact = ({ className, ...props }: ArtifactProps) => ( <Card data-slot="ai-artifact" className={cn("w-full gap-0 py-0", className)} {...props} />);
export type ArtifactHeaderProps = HTMLAttributes<HTMLDivElement>;
export const ArtifactHeader = ({ className, ...props}: ArtifactHeaderProps) => ( <div data-slot="ai-artifact-header" className={cn( "flex items-center justify-between gap-2.5 border-b border-border px-3.5 py-3", className, )} {...props} />);
export type ArtifactCloseProps = ComponentProps<typeof Button>;
/** Close button; a 1px divider separates it from the actions before it. */export const ArtifactClose = ({ className, children, size = "icon-sm", variant = "ghost", ...props}: ArtifactCloseProps) => ( <Button className={cn( "relative ml-2 text-muted-foreground before:absolute before:top-1/2 before:-left-1.5 before:h-[18px] before:w-px before:-translate-y-1/2 before:bg-border hover:text-foreground", className, )} size={size} type="button" variant={variant} {...props} > {children ?? ( <XIcon /> )} <span className="sr-only">Close</span> </Button>);
export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => ( <p className={cn("text-sm font-semibold text-foreground", className)} {...props} />);
export type ArtifactDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
export const ArtifactDescription = ({ className, ...props}: ArtifactDescriptionProps) => ( <p className={cn("text-xs text-muted-foreground", className)} {...props} />);
export type ArtifactActionsProps = HTMLAttributes<HTMLDivElement>;
export const ArtifactActions = ({ className, ...props}: ArtifactActionsProps) => ( <div className={cn("flex items-center gap-1", className)} {...props} />);
export type ArtifactActionProps = ComponentProps<typeof Button> & { tooltip?: string; label?: string; /** The icon element (an icon element); `children` works too. */ icon?: ReactNode;};
export const ArtifactAction = ({ tooltip, label, icon, children, className, size = "icon-sm", variant = "ghost", ...props}: ArtifactActionProps) => { const button = ( <Button className={cn("text-muted-foreground hover:text-foreground", className)} size={size} type="button" variant={variant} {...props} > {icon ?? children} <span className="sr-only">{label || tooltip}</span> </Button> );
if (tooltip) { return ( <TooltipProvider> <Tooltip> <TooltipTrigger render={button} /> <TooltipContent> <p>{tooltip}</p> </TooltipContent> </Tooltip> </TooltipProvider> ); }
return button;};
export type ArtifactContentProps = HTMLAttributes<HTMLDivElement>;
/** Scrollable content. No padding by default (code blocks and images run edge to edge); add `p-4` for prose. */export const ArtifactContent = ({ className, ...props}: ArtifactContentProps) => ( <div data-slot="ai-artifact-content" className={cn("flex-1 overflow-auto", className)} {...props} />);<script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { Card } from "@/registry/edmi/ui/card"import type { Elevation } from "@/registry/edmi/ui/elevation"
// Container for generated output. Built on the ui card; `elevation` ✦ is forwarded to the card.const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] /** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation}>(), { elevation: undefined,})</script>
<template> <Card data-slot="ai-artifact" :elevation="props.elevation" :class="cn('w-full gap-0 py-0', props.class)" > <slot /> </Card></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import type { ButtonVariants } from "@/registry/edmi/ui/button"import { cn } from "@/registry/edmi/lib/utils"import { Button } from "@/registry/edmi/ui/button"import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/registry/edmi/ui/tooltip"
defineOptions({ inheritAttrs: false })
// The icon goes in the default slot (an icon component), e.g. `<ArtifactAction tooltip="Copy"><CopyIcon /></ArtifactAction>`.const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] tooltip?: string label?: string variant?: ButtonVariants["variant"] size?: ButtonVariants["size"]}>(), { variant: "ghost", size: "icon-sm",})</script>
<template> <TooltipProvider v-if="props.tooltip"> <Tooltip> <TooltipTrigger as-child> <Button v-bind="$attrs" type="button" :variant="props.variant" :size="props.size" :class="cn('text-muted-foreground hover:text-foreground', props.class)" > <slot /> <span class="sr-only">{{ props.label || props.tooltip }}</span> </Button> </TooltipTrigger> <TooltipContent> <p>{{ props.tooltip }}</p> </TooltipContent> </Tooltip> </TooltipProvider> <Button v-else v-bind="$attrs" type="button" :variant="props.variant" :size="props.size" :class="cn('text-muted-foreground hover:text-foreground', props.class)" > <slot /> <span class="sr-only">{{ props.label }}</span> </Button></template><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 :class="cn('flex items-center gap-1', props.class)"> <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import type { ButtonVariants } from "@/registry/edmi/ui/button"import { XIcon } from "@lucide/vue"import { cn } from "@/registry/edmi/lib/utils"import { Button } from "@/registry/edmi/ui/button"
// Close button; a 1px divider separates it from the actions before it.const props = withDefaults(defineProps<{ class?: HTMLAttributes["class"] variant?: ButtonVariants["variant"] size?: ButtonVariants["size"]}>(), { variant: "ghost", size: "icon-sm",})</script>
<template> <Button type="button" :variant="props.variant" :size="props.size" :class="cn( 'relative ml-2 text-muted-foreground before:absolute before:top-1/2 before:-left-1.5 before:h-[18px] before:w-px before:-translate-y-1/2 before:bg-border hover:text-foreground', props.class, )" > <slot> <XIcon /> </slot> <span class="sr-only">Close</span> </Button></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
// Scrollable content. No padding by default (code blocks and images run edge to edge); add `p-4` for prose.const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <div data-slot="ai-artifact-content" :class="cn('flex-1 overflow-auto', props.class)"> <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <p :class="cn('text-xs text-muted-foreground', props.class)"> <slot /> </p></template><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-header" :class="cn('flex items-center justify-between gap-2.5 border-b border-border px-3.5 py-3', props.class)" > <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <p :class="cn('text-sm font-semibold text-foreground', props.class)"> <slot /> </p></template>export { default as Artifact } from "./Artifact.vue";export { default as ArtifactAction } from "./ArtifactAction.vue";export { default as ArtifactActions } from "./ArtifactActions.vue";export { default as ArtifactClose } from "./ArtifactClose.vue";export { default as ArtifactContent } from "./ArtifactContent.vue";export { default as ArtifactDescription } from "./ArtifactDescription.vue";export { default as ArtifactHeader } from "./ArtifactHeader.vue";export { default as ArtifactTitle } from "./ArtifactTitle.vue";<script lang="ts"> import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js"; import * as Tooltip from "$lib/registry/ui/tooltip/index.js"; import { cn } from "$lib/utils.js";
// The icon goes in `children` (an `IconPlaceholder`). let { tooltip, label, variant = "ghost", size = "icon-sm", class: className, children, ...restProps }: Omit<ButtonProps, "href"> & { tooltip?: string; label?: string } = $props();</script>
{#snippet inner()} {@render children?.()} <span class="sr-only">{label || tooltip}</span>{/snippet}
{#if tooltip} <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger> {#snippet child({ props })} <Button {...props} type="button" {variant} {size} class={cn("text-muted-foreground hover:text-foreground", className)} {...restProps} > {@render inner()} </Button> {/snippet} </Tooltip.Trigger> <Tooltip.Content> <p>{tooltip}</p> </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider>{:else} <Button type="button" {variant} {size} class={cn("text-muted-foreground hover:text-foreground", className)} {...restProps} > {@render inner()} </Button>{/if}<script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();</script>
<div class={cn("flex items-center gap-1", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import XIcon from "phosphor-svelte/lib/X"; import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js"; import { cn } from "$lib/utils.js";
// Close button; a 1px divider separates it from the actions before it. let { variant = "ghost", size = "icon-sm", class: className, children, ...restProps }: Omit<ButtonProps, "href"> = $props();</script>
<Button type="button" {variant} {size} class={cn( "relative ml-2 text-muted-foreground before:absolute before:top-1/2 before:-left-1.5 before:h-[18px] before:w-px before:-translate-y-1/2 before:bg-border hover:text-foreground", className )} {...restProps}> {#if children} {@render children()} {:else} <XIcon /> {/if} <span class="sr-only">Close</span></Button><script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();</script>
<div data-slot="ai-artifact-content" class={cn("flex-1 overflow-auto", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLParagraphElement> & { children?: Snippet } = $props();</script>
<p class={cn("text-xs text-muted-foreground", className)} {...restProps}> {@render children?.()}</p><script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();</script>
<div data-slot="ai-artifact-header" class={cn("flex items-center justify-between gap-2.5 border-b border-border px-3.5 py-3", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLParagraphElement> & { children?: Snippet } = $props();</script>
<p class={cn("text-sm font-semibold text-foreground", className)} {...restProps}> {@render children?.()}</p><script lang="ts"> import { Card } from "$lib/registry/ui/card/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
// Container for generated output. Built on the ui card; `elevation` ✦ is forwarded to the card (Card props). let { class: className, children, ...restProps }: ComponentProps<typeof Card> = $props();</script>
<Card data-slot="ai-artifact" class={cn("w-full gap-0 py-0", className)} {...restProps}> {@render children?.()}</Card>import Root from "./artifact.svelte";import Action from "./artifact-action.svelte";import Actions from "./artifact-actions.svelte";import Close from "./artifact-close.svelte";import Content from "./artifact-content.svelte";import Description from "./artifact-description.svelte";import Header from "./artifact-header.svelte";import Title from "./artifact-title.svelte";
export { Action as ArtifactAction, Actions as ArtifactActions, Close as ArtifactClose, Content as ArtifactContent, Description as ArtifactDescription, Header as ArtifactHeader, Root as Artifact, Title as ArtifactTitle,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
The container 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.
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
import { Artifact, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";
const levels = [ { value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex w-full max-w-2xl flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Artifact elevation={value} className="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactClose /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <p className="font-mono text-xs text-muted-foreground"> export async function run(index: string) {"{"} … {"}"} </p> </ArtifactContent> </Artifact> </div> ))} </div> );}<script setup lang="ts">import { Artifact, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle,} from "@/components/ai/artifact";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }];</script>
<template> <div class="flex w-full max-w-2xl flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <Artifact :elevation="level.value" class="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactClose /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <p class="font-mono text-xs text-muted-foreground"> export async function run(index: string) { … } </p> </ArtifactContent> </Artifact> </div> </div></template><script lang="ts"> import { Artifact, ArtifactActions, ArtifactClose, ArtifactContent, ArtifactDescription, ArtifactHeader, ArtifactTitle, } from "$lib/components/ai/artifact";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex w-full max-w-2xl flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <Artifact elevation={level.value} class="max-w-xl"> <ArtifactHeader> <div> <ArtifactTitle>rebalance.ts</ArtifactTitle> <ArtifactDescription>Generated · 8 lines</ArtifactDescription> </div> <ArtifactActions> <ArtifactClose /> </ArtifactActions> </ArtifactHeader> <ArtifactContent> <p class="font-mono text-xs text-muted-foreground"> export async function run(index: string) { … } </p> </ArtifactContent> </Artifact> </div> {/each}</div>| Part | Prop | Type | Default |
|---|---|---|---|
| Artifact | elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
| ArtifactAction | tooltip, label, icon |
string, string, ReactNode |
|
| ArtifactClose | Button props |
variant="ghost" |
|
| ArtifactContent | className |
string |
no padding |
✦ Edmi additions
Section titled “✦ Edmi additions”elevation: depth of the container (forwarded to the Card).ArtifactContenthas no padding by default so code blocks and images run edge to edge; addp-4for prose.ArtifactActiontakes the icon as a node (icon={<IconPlaceholder … />}) instead of a component.- Vue and Svelte: the action icon goes in the default slot / children instead of an
iconprop.