beUI Pro for mobile presale is open — Lifetime $129 for the until launch
AI Artifacts
Composable artifact containers with headers, actions, content, and footers for generated documents, code, and custom outputs.
Code artifact
index.tsxA compact code artifact using the public beUI agent code renderer, with syntax highlighting, line numbers, copy, and download.
group-tasks.ts
Task grouping utility
"use client";
import { Code2, Copy, Download } from "lucide-react";
import {
Artifact,
ArtifactAction,
ArtifactActions,
ArtifactClose,
ArtifactCode,
ArtifactContent,
ArtifactDescription,
ArtifactFooter,
ArtifactHeader,
ArtifactTitle,
} from "@/components/premium/ai-artifacts";
const code = `type Task = {
id: string;
title: string;
completed: boolean;
};
export function groupTasks(tasks: Task[]) {
return {
pending: tasks.filter((task) => !task.completed),
completed: tasks.filter((task) => task.completed),
};
}`;
export function AICodeArtifactPreview() {
return (
<div className="flex min-h-96 w-full items-center justify-center p-4 sm:p-6">
<Artifact className="max-w-2xl" aria-label="Code artifact">
<ArtifactHeader>
<div className="flex min-w-0 flex-1 items-center gap-3">
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-muted">
<Code2 className="size-4 text-muted-foreground" />
</span>
<div className="min-w-0">
<ArtifactTitle>group-tasks.ts</ArtifactTitle>
<ArtifactDescription>Task grouping utility</ArtifactDescription>
</div>
</div>
<ArtifactActions>
<ArtifactAction
label="Copy code"
tooltip="Copy code"
icon={Copy}
aria-disabled="true"
/>
<ArtifactAction
label="Download code"
tooltip="Download code"
icon={Download}
aria-disabled="true"
/>
<ArtifactClose aria-disabled="true" />
</ArtifactActions>
</ArtifactHeader>
<ArtifactContent className="p-0">
<ArtifactCode code={code} language="typescript" showLineNumbers />
</ArtifactContent>
<ArtifactFooter>
<span className="text-xs text-muted-foreground">
{code.split("\n").length} lines
</span>
<span className="text-xs text-muted-foreground">Version 1</span>
</ArtifactFooter>
</Artifact>
</div>
);
}
Source code is locked
Get access to install premium components and view the full source.
Open dashboardDocument artifact
index.tsxPreview and edit a document, revisit saved versions, copy content, and download Markdown.
A calmer way to plan
Markdown document
A calmer way to plan
Good work starts with a little clarity. We’re building a workspace that helps small teams turn scattered ideas into a plan everyone can follow.
Less organizing. More doing.
Bring your notes, decisions, and next steps together in one place. Keep the context beside the work, so nobody needs to search through yesterday’s messages to understand what comes next.
Built around your day
Start with an idea, shape it with your team, and leave with a clear next step. A quiet interface keeps your attention on the work while the details stay within reach.
Start small
Choose one project. Invite a teammate. Write down the next three things you want to get done. That’s enough to get moving.
"use client";
import {
Check,
Copy,
Download,
FileText,
History,
RotateCcw,
X,
} from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useId, useState } from "react";
import { Button } from "@/components/motion/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/motion/tabs";
import {
Artifact,
ArtifactAction,
ArtifactActions,
ArtifactContent,
ArtifactDescription,
ArtifactFooter,
ArtifactHeader,
ArtifactTitle,
} from "@/components/premium/ai-artifacts";
import { EASE_OUT } from "@/lib/ease";
const sampleDocument = {
title: "A calmer way to plan",
content:
"# A calmer way to plan\n\nGood work starts with a little clarity. We’re building a workspace that helps small teams turn scattered ideas into a plan everyone can follow.\n\n## Less organizing. More doing.\n\nBring your notes, decisions, and next steps together in one place. Keep the context beside the work, so nobody needs to search through yesterday’s messages to understand what comes next.\n\n## Built around your day\n\nStart with an idea, shape it with your team, and leave with a clear next step. A quiet interface keeps your attention on the work while the details stay within reach.\n\n## Start small\n\nChoose one project. Invite a teammate. Write down the next three things you want to get done. That’s enough to get moving.",
};
type ArtifactDocument = { title: string; content: string };
type Revision = ArtifactDocument & { id: number; label: string };
function DocumentPreview({ content }: { content: string }) {
return (
<div className="mx-auto max-w-none space-y-3 text-sm leading-6">
{content.split(/\n\n+/).map((paragraph, index) => {
if (paragraph.startsWith("# "))
return (
<h2
key={`${index}-${paragraph}`}
className="text-xl font-medium leading-tight tracking-tight"
>
{paragraph.slice(2)}
</h2>
);
if (paragraph.startsWith("## "))
return (
<h3
key={`${index}-${paragraph}`}
className="pt-3 text-base font-medium"
>
{paragraph.slice(3)}
</h3>
);
return (
<p
key={`${index}-${paragraph}`}
className="whitespace-pre-wrap text-muted-foreground"
>
{paragraph}
</p>
);
})}
</div>
);
}
export function AIArtifactPreview() {
const initialDocument = sampleDocument;
const [revisions, setRevisions] = useState<Revision[]>([
{ ...initialDocument, id: 1, label: "First draft" },
]);
const [activeId, setActiveId] = useState(1);
const active = revisions.find((item) => item.id === activeId) ?? revisions[0];
const [draft, setDraft] = useState(initialDocument.content);
const [mode, setMode] = useState("preview");
const [history, setHistory] = useState(false);
const historyId = useId();
const [notice, setNotice] = useState("");
const reduce = useReducedMotion();
const dirty = draft !== active.content;
function addRevision(document: ArtifactDocument, label: string) {
const id = revisions.length + 1;
setRevisions((current) => [...current, { ...document, id, label }]);
setActiveId(id);
setDraft(document.content);
setNotice("New version saved.");
}
return (
<div className="w-full p-4 sm:p-6">
<Artifact
aria-label="Document artifact"
onKeyDown={(event) => {
if (event.key === "Escape" && history) {
event.stopPropagation();
setHistory(false);
}
}}
className="mx-auto max-w-2xl"
>
<ArtifactHeader>
<div className="flex min-w-0 flex-1 items-center gap-3">
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-muted">
<FileText className="size-4 text-muted-foreground" />
</span>
<div className="min-w-0">
<ArtifactTitle>{active.title}</ArtifactTitle>
<ArtifactDescription>Markdown document</ArtifactDescription>
</div>
</div>
<ArtifactActions>
<ArtifactAction
label="Copy document"
tooltip="Copy document"
icon={Copy}
aria-disabled="true"
/>
<ArtifactAction
label="Download Markdown"
tooltip="Download Markdown"
aria-disabled="true"
>
<Download className="size-4" />
</ArtifactAction>
<ArtifactAction
label="Version history"
tooltip="Version history"
aria-expanded={history}
aria-controls={historyId}
className={history ? "bg-muted text-foreground" : undefined}
onClick={() => setHistory(!history)}
>
<History className="size-4" />
</ArtifactAction>
</ArtifactActions>
</ArtifactHeader>
<div className="flex items-center justify-between gap-3 border-b border-border px-4 py-1.5">
<Tabs
variant="pill"
value={mode}
onValueChange={(value) => {
setMode(value);
}}
>
<TabsList aria-label="Document view" className="gap-0 bg-muted p-1">
<TabsTrigger
value="preview"
indicatorClassName="bg-background"
className="h-7 min-w-20 px-3 py-0 text-xs aria-selected:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
Preview
</TabsTrigger>
<TabsTrigger
value="edit"
indicatorClassName="bg-background"
className="h-7 min-w-20 px-3 py-0 text-xs aria-selected:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
Edit
</TabsTrigger>
</TabsList>
</Tabs>
<span className="text-xs text-muted-foreground">
{dirty
? "Unsaved changes"
: `Version ${revisions.indexOf(active) + 1}`}
</span>
</div>
<div className="relative isolate">
<ArtifactContent className="h-80 px-5 py-5 sm:px-6">
{mode === "preview" ? (
<DocumentPreview content={draft} />
) : (
<textarea
aria-label="Edit document Markdown"
value={draft}
onChange={(event) => {
setDraft(event.target.value);
}}
className="h-full min-h-64 w-full resize-none rounded-lg bg-transparent p-2 text-sm leading-7 outline-none focus-visible:ring-2 focus-visible:ring-ring"
/>
)}
</ArtifactContent>
<AnimatePresence initial={false}>
{history && (
<motion.section
initial={
reduce ? false : { opacity: 0, transform: "translateX(12px)" }
}
animate={{ opacity: 1, transform: "translateX(0)" }}
exit={{
opacity: 0,
transform: reduce ? "none" : "translateX(12px)",
}}
transition={{ duration: reduce ? 0 : 0.18, ease: EASE_OUT }}
id={historyId}
aria-label="Version history"
className="absolute inset-y-0 right-0 z-10 w-full max-w-xs overflow-y-auto border-l border-border bg-background p-5"
>
<div className="mb-4 flex items-center justify-between gap-3">
<p className="text-sm font-medium">Version history</p>
<ArtifactAction
label="Close version history"
tooltip="Close history"
icon={X}
onClick={() => setHistory(false)}
/>
</div>
<p className="mb-4 text-xs leading-5 text-muted-foreground">
{dirty
? "Save or discard edits before switching versions."
: "Open an earlier draft to review or continue from it."}
</p>
{[...revisions].reverse().map((revision) => (
<Button
key={revision.id}
variant="ghost"
disabled={dirty}
onClick={() => {
setActiveId(revision.id);
setDraft(revision.content);
}}
className="mb-1 h-auto min-h-12 w-full justify-start gap-3 py-3"
>
<RotateCcw className="size-3.5 shrink-0" />
<span className="min-w-0 text-left">
<span className="block text-sm">
Version {revisions.indexOf(revision) + 1}
</span>
<span className="block truncate text-xs text-muted-foreground">
{revision.label}
</span>
</span>
{revision.id === activeId && (
<Check className="ml-auto size-4 shrink-0" />
)}
</Button>
))}
</motion.section>
)}
</AnimatePresence>
</div>
<ArtifactFooter>
<span className="text-xs text-muted-foreground">
{draft.trim() ? draft.trim().split(/\s+/).length : 0} words
</span>
{dirty ? (
<div className="flex gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
setDraft(active.content);
}}
>
Discard
</Button>
<Button
size="sm"
disabled={!draft.trim()}
onClick={() =>
addRevision(
{ title: active.title, content: draft },
"Manual edit",
)
}
>
Save version
</Button>
</div>
) : (
<span className="text-xs text-muted-foreground">
{mode === "edit" ? "Editing Markdown" : "All changes saved"}
</span>
)}
</ArtifactFooter>
<output className="sr-only">{notice}</output>
</Artifact>
</div>
);
}
Source code is locked
Get access to install premium components and view the full source.
Open dashboardInstall
Add this component with the shadcn CLI.