diff --git a/packages/create-llama/create-app.ts b/packages/create-llama/create-app.ts
index f37c4c40ad12dfe94443d54619a8c15ace24586c..ea5a0ac815f40b0529aefd30e0555fd4c11edbb2 100644
--- a/packages/create-llama/create-app.ts
+++ b/packages/create-llama/create-app.ts
@@ -12,6 +12,7 @@ import type {
   TemplateEngine,
   TemplateFramework,
   TemplateType,
+  TemplateUI,
 } from "./templates";
 import { installTemplate } from "./templates";
 
@@ -19,6 +20,7 @@ export async function createApp({
   template,
   framework,
   engine,
+  ui,
   appPath,
   packageManager,
   eslint,
@@ -26,6 +28,7 @@ export async function createApp({
   template: TemplateType;
   framework: TemplateFramework;
   engine: TemplateEngine;
+  ui: TemplateUI;
   appPath: string;
   packageManager: PackageManager;
   eslint: boolean;
@@ -63,6 +66,7 @@ export async function createApp({
     template,
     framework,
     engine,
+    ui,
     packageManager,
     isOnline,
     eslint,
diff --git a/packages/create-llama/index.ts b/packages/create-llama/index.ts
index ab80066569c681989a8cf3959a0ca9fbac7c9680..6aeb13ecba52916ffdbe2760b0620aed99a6dbf4 100644
--- a/packages/create-llama/index.ts
+++ b/packages/create-llama/index.ts
@@ -180,6 +180,7 @@ async function run(): Promise<void> {
     template: "simple",
     framework: "nextjs",
     engine: "simple",
+    ui: "html",
     eslint: true,
   };
   const getPrefOrDefault = (field: string) =>
@@ -239,6 +240,35 @@ async function run(): Promise<void> {
     }
   }
 
+  if (program.framework === "nextjs") {
+    if (!program.ui) {
+      if (ciInfo.isCI) {
+        program.ui = getPrefOrDefault("ui");
+      } else {
+        const { ui } = await prompts(
+          {
+            type: "select",
+            name: "ui",
+            message: "Which UI would you like to use?",
+            choices: [
+              { title: "Just HTML", value: "html" },
+              { title: "Shadcn", value: "shadcn" },
+            ],
+            initial: 0,
+          },
+          {
+            onCancel: () => {
+              console.error("Exiting.");
+              process.exit(1);
+            },
+          },
+        );
+        program.ui = ui;
+        preferences.ui = ui;
+      }
+    }
+  }
+
   if (program.framework === "express" || program.framework === "nextjs") {
     if (!program.engine) {
       if (ciInfo.isCI) {
@@ -294,6 +324,7 @@ async function run(): Promise<void> {
     template: program.template,
     framework: program.framework,
     engine: program.engine,
+    ui: program.ui,
     appPath: resolvedProjectPath,
     packageManager,
     eslint: program.eslint,
diff --git a/packages/create-llama/templates/index.ts b/packages/create-llama/templates/index.ts
index 92641318f17d2460bb7852556e8b244c0ae95cce..5bf8a7d3b2e1fbea897c702c32db32ac2fad7737 100644
--- a/packages/create-llama/templates/index.ts
+++ b/packages/create-llama/templates/index.ts
@@ -20,6 +20,7 @@ export const installTemplate = async ({
   template,
   framework,
   engine,
+  ui,
   eslint,
 }: InstallTemplateArgs) => {
   console.log(bold(`Using ${packageManager}.`));
@@ -81,6 +82,26 @@ export const installTemplate = async ({
     await fs.writeFile(routeFile, newContent);
   }
 
+  /**
+   * Copy the selected UI files to the target directory and reference it.
+   */
+  if (framework === "nextjs") {
+    console.log("\nUsing UI:", ui, "\n");
+    const uiPath = path.join(__dirname, "ui", ui);
+    const componentsPath = path.join("app", "components");
+    await copy("**", path.join(root, componentsPath, "ui"), {
+      parents: true,
+      cwd: uiPath,
+    });
+    const chatSectionFile = path.join(root, componentsPath, "chat-section.tsx");
+    const chatSectionFileContent = await fs.readFile(chatSectionFile, "utf8");
+    const newContent = chatSectionFileContent.replace(
+      /^import { ChatInput, ChatMessages, Message }.*$/m,
+      'import { ChatInput, ChatMessages, Message } from "./ui/chat"\n',
+    );
+    await fs.writeFile(chatSectionFile, newContent);
+  }
+
   /**
    * Update the package.json scripts.
    */
@@ -108,6 +129,17 @@ export const installTemplate = async ({
     };
   }
 
+  if (framework === "nextjs" && ui === "shadcn") {
+    // add shadcn dependencies to package.json
+    packageJson.dependencies = {
+      ...packageJson.dependencies,
+      "tailwind-merge": "^2",
+      "@radix-ui/react-slot": "^1",
+      "class-variance-authority": "^0.7",
+      "lucide-react": "^0.291",
+    };
+  }
+
   if (!eslint) {
     // Remove packages starting with "eslint" from devDependencies
     packageJson.devDependencies = Object.fromEntries(
diff --git a/packages/create-llama/templates/simple/nextjs/app/components/chat-section.tsx b/packages/create-llama/templates/simple/nextjs/app/components/chat-section.tsx
index 989bfcda4b29777235db1d29fdfaa483d047b1e5..0a3ea5b3d5135354745289fccd3e16f0db2179ae 100644
--- a/packages/create-llama/templates/simple/nextjs/app/components/chat-section.tsx
+++ b/packages/create-llama/templates/simple/nextjs/app/components/chat-section.tsx
@@ -2,8 +2,7 @@
 
 import { nanoid } from "nanoid";
 import { useState } from "react";
-import ChatInput from "./ui/chat-input";
-import ChatMessages, { Message } from "./ui/chat-messages";
+import { ChatInput, ChatMessages, Message } from "../../../../ui/html/chat";
 
 export default function ChatSection() {
   const [messages, setMessages] = useState<Message[]>([]);
@@ -37,7 +36,7 @@ export default function ChatSection() {
       setMessages(newMessages);
       setInput("");
       const assistantMessage = await getAssistantMessage(newMessages);
-      setMessages([...newMessages, { ...assistantMessage }]);
+      setMessages([...newMessages, { ...assistantMessage, id: nanoid() }]);
       setLoading(false);
     } catch (error: any) {
       alert(JSON.stringify(error));
diff --git a/packages/create-llama/templates/simple/nextjs/app/globals.css b/packages/create-llama/templates/simple/nextjs/app/globals.css
index d85e2eec9ab40d8bc2d8cbad401f414ae8cd0ab2..09b85ed2c912e25518ddebbfebaba69090f889f4 100644
--- a/packages/create-llama/templates/simple/nextjs/app/globals.css
+++ b/packages/create-llama/templates/simple/nextjs/app/globals.css
@@ -2,38 +2,93 @@
 @tailwind components;
 @tailwind utilities;
 
-:root {
-  --foreground-rgb: 0, 0, 0;
-  --background-start-rgb: 214, 219, 220;
-  --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
+@layer base {
   :root {
-    --foreground-rgb: 255, 255, 255;
-    --background-start-rgb: 0, 0, 0;
-    --background-end-rgb: 0, 0, 0;
+    --background: 0 0% 100%;
+    --foreground: 222.2 47.4% 11.2%;
+
+    --muted: 210 40% 96.1%;
+    --muted-foreground: 215.4 16.3% 46.9%;
+
+    --popover: 0 0% 100%;
+    --popover-foreground: 222.2 47.4% 11.2%;
+
+    --border: 214.3 31.8% 91.4%;
+    --input: 214.3 31.8% 91.4%;
+
+    --card: 0 0% 100%;
+    --card-foreground: 222.2 47.4% 11.2%;
+
+    --primary: 222.2 47.4% 11.2%;
+    --primary-foreground: 210 40% 98%;
+
+    --secondary: 210 40% 96.1%;
+    --secondary-foreground: 222.2 47.4% 11.2%;
+
+    --accent: 210 40% 96.1%;
+    --accent-foreground: 222.2 47.4% 11.2%;
+
+    --destructive: 0 100% 50%;
+    --destructive-foreground: 210 40% 98%;
+
+    --ring: 215 20.2% 65.1%;
+
+    --radius: 0.5rem;
   }
-}
 
-body {
-  color: rgb(var(--foreground-rgb));
-  background: linear-gradient(
-      to bottom,
-      transparent,
-      rgb(var(--background-end-rgb))
-    )
-    rgb(var(--background-start-rgb));
+  .dark {
+    --background: 224 71% 4%;
+    --foreground: 213 31% 91%;
+
+    --muted: 223 47% 11%;
+    --muted-foreground: 215.4 16.3% 56.9%;
+
+    --accent: 216 34% 17%;
+    --accent-foreground: 210 40% 98%;
+
+    --popover: 224 71% 4%;
+    --popover-foreground: 215 20.2% 65.1%;
+
+    --border: 216 34% 17%;
+    --input: 216 34% 17%;
+
+    --card: 224 71% 4%;
+    --card-foreground: 213 31% 91%;
+
+    --primary: 210 40% 98%;
+    --primary-foreground: 222.2 47.4% 1.2%;
+
+    --secondary: 222.2 47.4% 11.2%;
+    --secondary-foreground: 210 40% 98%;
+
+    --destructive: 0 63% 31%;
+    --destructive-foreground: 210 40% 98%;
+
+    --ring: 216 34% 17%;
+
+    --radius: 0.5rem;
+  }
 }
 
-.background-gradient {
-  background-color: #fff;
-  background-image: radial-gradient(
-      at 21% 11%,
-      rgba(186, 186, 233, 0.53) 0,
-      transparent 50%
-    ),
-    radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%),
-    radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%),
-    radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%);
+@layer base {
+  * {
+    @apply border-border;
+  }
+  body {
+    @apply bg-background text-foreground;
+    font-feature-settings:
+      "rlig" 1,
+      "calt" 1;
+  }
+  .background-gradient {
+    background-color: #fff;
+    background-image: radial-gradient(
+        at 21% 11%,
+        rgba(186, 186, 233, 0.53) 0,
+        transparent 50%
+      ),
+      radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%),
+      radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%),
+      radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%);
+  }
 }
diff --git a/packages/create-llama/templates/simple/nextjs/tailwind.config.ts b/packages/create-llama/templates/simple/nextjs/tailwind.config.ts
index 7e4bd91a03437328466a264489ce47e107635565..aa5580affac868255fedb5a8ddc0dde7a105c454 100644
--- a/packages/create-llama/templates/simple/nextjs/tailwind.config.ts
+++ b/packages/create-llama/templates/simple/nextjs/tailwind.config.ts
@@ -1,17 +1,75 @@
 import type { Config } from "tailwindcss";
+import { fontFamily } from "tailwindcss/defaultTheme";
 
 const config: Config = {
-  content: [
-    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
-    "./components/**/*.{js,ts,jsx,tsx,mdx}",
-    "./app/**/*.{js,ts,jsx,tsx,mdx}",
-  ],
+  darkMode: ["class"],
+  content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
   theme: {
+    container: {
+      center: true,
+      padding: "2rem",
+      screens: {
+        "2xl": "1400px",
+      },
+    },
     extend: {
-      backgroundImage: {
-        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
-        "gradient-conic":
-          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+      colors: {
+        border: "hsl(var(--border))",
+        input: "hsl(var(--input))",
+        ring: "hsl(var(--ring))",
+        background: "hsl(var(--background))",
+        foreground: "hsl(var(--foreground))",
+        primary: {
+          DEFAULT: "hsl(var(--primary))",
+          foreground: "hsl(var(--primary-foreground))",
+        },
+        secondary: {
+          DEFAULT: "hsl(var(--secondary))",
+          foreground: "hsl(var(--secondary-foreground))",
+        },
+        destructive: {
+          DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
+          foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
+        },
+        muted: {
+          DEFAULT: "hsl(var(--muted))",
+          foreground: "hsl(var(--muted-foreground))",
+        },
+        accent: {
+          DEFAULT: "hsl(var(--accent))",
+          foreground: "hsl(var(--accent-foreground))",
+        },
+        popover: {
+          DEFAULT: "hsl(var(--popover))",
+          foreground: "hsl(var(--popover-foreground))",
+        },
+        card: {
+          DEFAULT: "hsl(var(--card))",
+          foreground: "hsl(var(--card-foreground))",
+        },
+      },
+      borderRadius: {
+        xl: `calc(var(--radius) + 4px)`,
+        lg: `var(--radius)`,
+        md: `calc(var(--radius) - 2px)`,
+        sm: "calc(var(--radius) - 4px)",
+      },
+      fontFamily: {
+        sans: ["var(--font-sans)", ...fontFamily.sans],
+      },
+      keyframes: {
+        "accordion-down": {
+          from: { height: "0" },
+          to: { height: "var(--radix-accordion-content-height)" },
+        },
+        "accordion-up": {
+          from: { height: "var(--radix-accordion-content-height)" },
+          to: { height: "0" },
+        },
+      },
+      animation: {
+        "accordion-down": "accordion-down 0.2s ease-out",
+        "accordion-up": "accordion-up 0.2s ease-out",
       },
     },
   },
diff --git a/packages/create-llama/templates/streaming/nextjs/app/components/chat-section.tsx b/packages/create-llama/templates/streaming/nextjs/app/components/chat-section.tsx
index 5ef09b3343ae357de8a88071306c6c39b638d425..0a5fcef939a304d351194b268bb84201f7865072 100644
--- a/packages/create-llama/templates/streaming/nextjs/app/components/chat-section.tsx
+++ b/packages/create-llama/templates/streaming/nextjs/app/components/chat-section.tsx
@@ -1,8 +1,7 @@
 "use client";
 
-import ChatInput from "@/app/components/ui/chat-input";
 import { useChat } from "ai/react";
-import ChatMessages from "./ui/chat-messages";
+import { ChatInput, ChatMessages, Message } from "../../../../ui/html/chat";
 
 export default function ChatSection() {
   const { messages, input, isLoading, handleSubmit, handleInputChange } =
diff --git a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-avatar.tsx b/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-avatar.tsx
deleted file mode 100644
index cd241104e4ef210c728aec47a1ab8b0161ad6538..0000000000000000000000000000000000000000
--- a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-avatar.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import { Message } from "./chat-messages";
-
-export default function ChatAvatar(message: Message) {
-  if (message.role === "user") {
-    return (
-      <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow bg-background">
-        <svg
-          xmlns="http://www.w3.org/2000/svg"
-          viewBox="0 0 256 256"
-          fill="currentColor"
-          className="h-4 w-4"
-        >
-          <path d="M230.92 212c-15.23-26.33-38.7-45.21-66.09-54.16a72 72 0 1 0-73.66 0c-27.39 8.94-50.86 27.82-66.09 54.16a8 8 0 1 0 13.85 8c18.84-32.56 52.14-52 89.07-52s70.23 19.44 89.07 52a8 8 0 1 0 13.85-8ZM72 96a56 56 0 1 1 56 56 56.06 56.06 0 0 1-56-56Z"></path>
-        </svg>
-      </div>
-    );
-  }
-
-  return (
-    <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border  bg-black text-white">
-      <Image
-        className="rounded-md"
-        src="/llama.png"
-        alt="Llama Logo"
-        width={24}
-        height={24}
-        priority
-      />
-    </div>
-  );
-}
diff --git a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-input.tsx b/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-input.tsx
deleted file mode 100644
index 3eb979b02735943f3f11290c78b84f0e37709438..0000000000000000000000000000000000000000
--- a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-input.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client";
-
-export interface ChatInputProps {
-  /** The current value of the input */
-  input?: string;
-  /** An input/textarea-ready onChange handler to control the value of the input */
-  handleInputChange?: (
-    e:
-      | React.ChangeEvent<HTMLInputElement>
-      | React.ChangeEvent<HTMLTextAreaElement>,
-  ) => void;
-  /** Form submission handler to automatically reset input and append a user message  */
-  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
-  isLoading: boolean;
-}
-
-export default function ChatInput(props: ChatInputProps) {
-  return (
-    <>
-      <form
-        onSubmit={props.handleSubmit}
-        className="flex items-start justify-between w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl gap-4"
-      >
-        <input
-          autoFocus
-          name="message"
-          placeholder="Type a message"
-          className="w-full p-4 rounded-xl shadow-inner flex-1"
-          value={props.input}
-          onChange={props.handleInputChange}
-        />
-        <button
-          disabled={props.isLoading}
-          type="submit"
-          className="p-4 text-white rounded-xl shadow-xl bg-gradient-to-r from-cyan-500 to-sky-500 disabled:opacity-50 disabled:cursor-not-allowed"
-        >
-          Send message
-        </button>
-      </form>
-    </>
-  );
-}
diff --git a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-item.tsx b/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-item.tsx
deleted file mode 100644
index 2244f729a8ba668121ab5ec0842963d22153ef92..0000000000000000000000000000000000000000
--- a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-item.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-"use client";
-
-import ChatAvatar from "./chat-avatar";
-import { Message } from "./chat-messages";
-
-export default function ChatItem(message: Message) {
-  return (
-    <div className="flex items-start gap-4 pt-5">
-      <ChatAvatar {...message} />
-      <p className="break-words">{message.content}</p>
-    </div>
-  );
-}
diff --git a/packages/create-llama/templates/streaming/nextjs/app/globals.css b/packages/create-llama/templates/streaming/nextjs/app/globals.css
index d85e2eec9ab40d8bc2d8cbad401f414ae8cd0ab2..09b85ed2c912e25518ddebbfebaba69090f889f4 100644
--- a/packages/create-llama/templates/streaming/nextjs/app/globals.css
+++ b/packages/create-llama/templates/streaming/nextjs/app/globals.css
@@ -2,38 +2,93 @@
 @tailwind components;
 @tailwind utilities;
 
-:root {
-  --foreground-rgb: 0, 0, 0;
-  --background-start-rgb: 214, 219, 220;
-  --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
+@layer base {
   :root {
-    --foreground-rgb: 255, 255, 255;
-    --background-start-rgb: 0, 0, 0;
-    --background-end-rgb: 0, 0, 0;
+    --background: 0 0% 100%;
+    --foreground: 222.2 47.4% 11.2%;
+
+    --muted: 210 40% 96.1%;
+    --muted-foreground: 215.4 16.3% 46.9%;
+
+    --popover: 0 0% 100%;
+    --popover-foreground: 222.2 47.4% 11.2%;
+
+    --border: 214.3 31.8% 91.4%;
+    --input: 214.3 31.8% 91.4%;
+
+    --card: 0 0% 100%;
+    --card-foreground: 222.2 47.4% 11.2%;
+
+    --primary: 222.2 47.4% 11.2%;
+    --primary-foreground: 210 40% 98%;
+
+    --secondary: 210 40% 96.1%;
+    --secondary-foreground: 222.2 47.4% 11.2%;
+
+    --accent: 210 40% 96.1%;
+    --accent-foreground: 222.2 47.4% 11.2%;
+
+    --destructive: 0 100% 50%;
+    --destructive-foreground: 210 40% 98%;
+
+    --ring: 215 20.2% 65.1%;
+
+    --radius: 0.5rem;
   }
-}
 
-body {
-  color: rgb(var(--foreground-rgb));
-  background: linear-gradient(
-      to bottom,
-      transparent,
-      rgb(var(--background-end-rgb))
-    )
-    rgb(var(--background-start-rgb));
+  .dark {
+    --background: 224 71% 4%;
+    --foreground: 213 31% 91%;
+
+    --muted: 223 47% 11%;
+    --muted-foreground: 215.4 16.3% 56.9%;
+
+    --accent: 216 34% 17%;
+    --accent-foreground: 210 40% 98%;
+
+    --popover: 224 71% 4%;
+    --popover-foreground: 215 20.2% 65.1%;
+
+    --border: 216 34% 17%;
+    --input: 216 34% 17%;
+
+    --card: 224 71% 4%;
+    --card-foreground: 213 31% 91%;
+
+    --primary: 210 40% 98%;
+    --primary-foreground: 222.2 47.4% 1.2%;
+
+    --secondary: 222.2 47.4% 11.2%;
+    --secondary-foreground: 210 40% 98%;
+
+    --destructive: 0 63% 31%;
+    --destructive-foreground: 210 40% 98%;
+
+    --ring: 216 34% 17%;
+
+    --radius: 0.5rem;
+  }
 }
 
-.background-gradient {
-  background-color: #fff;
-  background-image: radial-gradient(
-      at 21% 11%,
-      rgba(186, 186, 233, 0.53) 0,
-      transparent 50%
-    ),
-    radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%),
-    radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%),
-    radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%);
+@layer base {
+  * {
+    @apply border-border;
+  }
+  body {
+    @apply bg-background text-foreground;
+    font-feature-settings:
+      "rlig" 1,
+      "calt" 1;
+  }
+  .background-gradient {
+    background-color: #fff;
+    background-image: radial-gradient(
+        at 21% 11%,
+        rgba(186, 186, 233, 0.53) 0,
+        transparent 50%
+      ),
+      radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%),
+      radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%),
+      radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%);
+  }
 }
diff --git a/packages/create-llama/templates/streaming/nextjs/tailwind.config.ts b/packages/create-llama/templates/streaming/nextjs/tailwind.config.ts
index 7e4bd91a03437328466a264489ce47e107635565..aa5580affac868255fedb5a8ddc0dde7a105c454 100644
--- a/packages/create-llama/templates/streaming/nextjs/tailwind.config.ts
+++ b/packages/create-llama/templates/streaming/nextjs/tailwind.config.ts
@@ -1,17 +1,75 @@
 import type { Config } from "tailwindcss";
+import { fontFamily } from "tailwindcss/defaultTheme";
 
 const config: Config = {
-  content: [
-    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
-    "./components/**/*.{js,ts,jsx,tsx,mdx}",
-    "./app/**/*.{js,ts,jsx,tsx,mdx}",
-  ],
+  darkMode: ["class"],
+  content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
   theme: {
+    container: {
+      center: true,
+      padding: "2rem",
+      screens: {
+        "2xl": "1400px",
+      },
+    },
     extend: {
-      backgroundImage: {
-        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
-        "gradient-conic":
-          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+      colors: {
+        border: "hsl(var(--border))",
+        input: "hsl(var(--input))",
+        ring: "hsl(var(--ring))",
+        background: "hsl(var(--background))",
+        foreground: "hsl(var(--foreground))",
+        primary: {
+          DEFAULT: "hsl(var(--primary))",
+          foreground: "hsl(var(--primary-foreground))",
+        },
+        secondary: {
+          DEFAULT: "hsl(var(--secondary))",
+          foreground: "hsl(var(--secondary-foreground))",
+        },
+        destructive: {
+          DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
+          foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
+        },
+        muted: {
+          DEFAULT: "hsl(var(--muted))",
+          foreground: "hsl(var(--muted-foreground))",
+        },
+        accent: {
+          DEFAULT: "hsl(var(--accent))",
+          foreground: "hsl(var(--accent-foreground))",
+        },
+        popover: {
+          DEFAULT: "hsl(var(--popover))",
+          foreground: "hsl(var(--popover-foreground))",
+        },
+        card: {
+          DEFAULT: "hsl(var(--card))",
+          foreground: "hsl(var(--card-foreground))",
+        },
+      },
+      borderRadius: {
+        xl: `calc(var(--radius) + 4px)`,
+        lg: `var(--radius)`,
+        md: `calc(var(--radius) - 2px)`,
+        sm: "calc(var(--radius) - 4px)",
+      },
+      fontFamily: {
+        sans: ["var(--font-sans)", ...fontFamily.sans],
+      },
+      keyframes: {
+        "accordion-down": {
+          from: { height: "0" },
+          to: { height: "var(--radix-accordion-content-height)" },
+        },
+        "accordion-up": {
+          from: { height: "var(--radix-accordion-content-height)" },
+          to: { height: "0" },
+        },
+      },
+      animation: {
+        "accordion-down": "accordion-down 0.2s ease-out",
+        "accordion-up": "accordion-up 0.2s ease-out",
       },
     },
   },
diff --git a/packages/create-llama/templates/types.ts b/packages/create-llama/templates/types.ts
index 4c9ad3c6538353d3ca1d74eb98f541d0ef6f9b34..e76cd7da17718c906b6c58dcd93c1c825d0aba9f 100644
--- a/packages/create-llama/templates/types.ts
+++ b/packages/create-llama/templates/types.ts
@@ -3,6 +3,7 @@ import { PackageManager } from "../helpers/get-pkg-manager";
 export type TemplateType = "simple" | "streaming";
 export type TemplateFramework = "nextjs" | "express";
 export type TemplateEngine = "simple" | "context";
+export type TemplateUI = "html" | "shadcn";
 
 export interface InstallTemplateArgs {
   appName: string;
@@ -12,5 +13,6 @@ export interface InstallTemplateArgs {
   template: TemplateType;
   framework: TemplateFramework;
   engine: TemplateEngine;
+  ui: TemplateUI;
   eslint: boolean;
 }
diff --git a/packages/create-llama/templates/simple/nextjs/app/components/ui/chat-avatar.tsx b/packages/create-llama/templates/ui/html/chat/chat-avatar.tsx
similarity index 100%
rename from packages/create-llama/templates/simple/nextjs/app/components/ui/chat-avatar.tsx
rename to packages/create-llama/templates/ui/html/chat/chat-avatar.tsx
diff --git a/packages/create-llama/templates/simple/nextjs/app/components/ui/chat-input.tsx b/packages/create-llama/templates/ui/html/chat/chat-input.tsx
similarity index 100%
rename from packages/create-llama/templates/simple/nextjs/app/components/ui/chat-input.tsx
rename to packages/create-llama/templates/ui/html/chat/chat-input.tsx
diff --git a/packages/create-llama/templates/simple/nextjs/app/components/ui/chat-item.tsx b/packages/create-llama/templates/ui/html/chat/chat-item.tsx
similarity index 100%
rename from packages/create-llama/templates/simple/nextjs/app/components/ui/chat-item.tsx
rename to packages/create-llama/templates/ui/html/chat/chat-item.tsx
diff --git a/packages/create-llama/templates/simple/nextjs/app/components/ui/chat-messages.tsx b/packages/create-llama/templates/ui/html/chat/chat-messages.tsx
similarity index 100%
rename from packages/create-llama/templates/simple/nextjs/app/components/ui/chat-messages.tsx
rename to packages/create-llama/templates/ui/html/chat/chat-messages.tsx
diff --git a/packages/create-llama/templates/ui/html/chat/index.ts b/packages/create-llama/templates/ui/html/chat/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4ccc54926fbd450313f952747bc1ea720100f2df
--- /dev/null
+++ b/packages/create-llama/templates/ui/html/chat/index.ts
@@ -0,0 +1,6 @@
+import ChatInput from "./chat-input";
+import ChatMessages from "./chat-messages";
+
+export type { ChatInputProps } from "./chat-input";
+export type { Message } from "./chat-messages";
+export { ChatMessages, ChatInput };
diff --git a/packages/create-llama/templates/ui/shadcn/button.tsx b/packages/create-llama/templates/ui/shadcn/button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..662b0404d83445eb7fca1ead6724e944610fdf25
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/button.tsx
@@ -0,0 +1,56 @@
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+
+import { cn } from "./lib/utils";
+
+const buttonVariants = cva(
+  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+  {
+    variants: {
+      variant: {
+        default: "bg-primary text-primary-foreground hover:bg-primary/90",
+        destructive:
+          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+        outline:
+          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+        secondary:
+          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        ghost: "hover:bg-accent hover:text-accent-foreground",
+        link: "text-primary underline-offset-4 hover:underline",
+      },
+      size: {
+        default: "h-10 px-4 py-2",
+        sm: "h-9 rounded-md px-3",
+        lg: "h-11 rounded-md px-8",
+        icon: "h-10 w-10",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  },
+);
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean;
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, ...props }, ref) => {
+    const Comp = asChild ? Slot : "button";
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        ref={ref}
+        {...props}
+      />
+    );
+  },
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/packages/create-llama/templates/ui/shadcn/chat/chat-avatar.tsx b/packages/create-llama/templates/ui/shadcn/chat/chat-avatar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ce04e306a7164e49e7ea6950a55c4f5cedc2ee2a
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/chat/chat-avatar.tsx
@@ -0,0 +1,25 @@
+import { User2 } from "lucide-react";
+import Image from "next/image";
+
+export default function ChatAvatar({ role }: { role: string }) {
+  if (role === "user") {
+    return (
+      <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-background shadow">
+        <User2 className="h-4 w-4" />
+      </div>
+    );
+  }
+
+  return (
+    <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white shadow">
+      <Image
+        className="rounded-md"
+        src="/llama.png"
+        alt="Llama Logo"
+        width={24}
+        height={24}
+        priority
+      />
+    </div>
+  );
+}
diff --git a/packages/create-llama/templates/ui/shadcn/chat/chat-input.tsx b/packages/create-llama/templates/ui/shadcn/chat/chat-input.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..86b079e0bc4b14d5c72d3f24c3a7641fddd2e153
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/chat/chat-input.tsx
@@ -0,0 +1,32 @@
+import * as React from "react";
+
+import { Button } from "../button";
+import { Input } from "../input";
+
+export interface ChatInputProps {
+  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
+  handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+  input: string;
+  isLoading: boolean;
+}
+
+export default function ChatInput(props: ChatInputProps) {
+  return (
+    <form
+      onSubmit={props.handleSubmit}
+      className="mx-auto flex w-full max-w-5xl items-start justify-between gap-4 rounded-xl bg-white p-4 shadow-xl"
+    >
+      <Input
+        autoFocus
+        name="message"
+        placeholder="Type a message"
+        className="flex-1"
+        value={props.input}
+        onChange={props.handleInputChange}
+      />
+      <Button disabled={props.isLoading} type="submit">
+        Send message
+      </Button>
+    </form>
+  );
+}
diff --git a/packages/create-llama/templates/ui/shadcn/chat/chat-message.tsx b/packages/create-llama/templates/ui/shadcn/chat/chat-message.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..62f7e75dd64f8f95b753a69e6934a72e7ab64bca
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/chat/chat-message.tsx
@@ -0,0 +1,44 @@
+import { Check, Copy } from "lucide-react";
+import { useState } from "react";
+
+import { Button } from "../button";
+import ChatAvatar from "./chat-avatar";
+
+export interface Message {
+  id: string;
+  content: string;
+  role: string;
+}
+
+export default function ChatMessage(chatMessage: Message) {
+  const [isCopied, setIsCopied] = useState(false);
+
+  const copyToClipboard = () => {
+    navigator.clipboard.writeText(chatMessage.content);
+    setIsCopied(true);
+    setTimeout(() => {
+      setIsCopied(false);
+    }, 2000);
+  };
+
+  return (
+    <div className="flex items-start gap-4 pt-5">
+      <ChatAvatar role={chatMessage.role} />
+      <div className="group flex flex-1 justify-between gap-2">
+        <p className="break-words">{chatMessage.content}</p>
+        <Button
+          onClick={copyToClipboard}
+          size="icon"
+          variant="ghost"
+          className="hidden h-8 w-8 group-hover:flex"
+        >
+          {isCopied ? (
+            <Check className="h-4 w-4" />
+          ) : (
+            <Copy className="h-4 w-4" />
+          )}
+        </Button>
+      </div>
+    </div>
+  );
+}
diff --git a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-messages.tsx b/packages/create-llama/templates/ui/shadcn/chat/chat-messages.tsx
similarity index 62%
rename from packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-messages.tsx
rename to packages/create-llama/templates/ui/shadcn/chat/chat-messages.tsx
index 65eacabbfb1b7bc99950b43e7fd07ba56b3ecdb0..d5162768c7e1db4d9963a6a6e50131e1700d1a1e 100644
--- a/packages/create-llama/templates/streaming/nextjs/app/components/ui/chat-messages.tsx
+++ b/packages/create-llama/templates/ui/shadcn/chat/chat-messages.tsx
@@ -1,13 +1,6 @@
-"use client";
-
 import { useEffect, useRef } from "react";
-import ChatItem from "./chat-item";
 
-export interface Message {
-  id: string;
-  content: string;
-  role: string;
-}
+import ChatMessage, { Message } from "./chat-message";
 
 export default function ChatMessages({ messages }: { messages: Message[] }) {
   const scrollableChatContainerRef = useRef<HTMLDivElement>(null);
@@ -24,13 +17,13 @@ export default function ChatMessages({ messages }: { messages: Message[] }) {
   }, [messages.length]);
 
   return (
-    <div className="w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl">
+    <div className="mx-auto w-full max-w-5xl rounded-xl bg-white p-4 shadow-xl">
       <div
-        className="flex flex-col gap-5 divide-y h-[50vh] overflow-auto"
+        className="flex h-[50vh] flex-col gap-5 divide-y overflow-auto"
         ref={scrollableChatContainerRef}
       >
-        {messages.map((m: Message) => (
-          <ChatItem key={m.id} {...m} />
+        {messages.map((m) => (
+          <ChatMessage key={m.id} {...m} />
         ))}
       </div>
     </div>
diff --git a/packages/create-llama/templates/ui/shadcn/chat/index.ts b/packages/create-llama/templates/ui/shadcn/chat/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d50251ffd6184259c6916a74951d88e0f598c6c5
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/chat/index.ts
@@ -0,0 +1,6 @@
+import ChatInput from "./chat-input";
+import ChatMessages from "./chat-messages";
+
+export type { ChatInputProps } from "./chat-input";
+export type { Message } from "./chat-message";
+export { ChatMessages, ChatInput };
diff --git a/packages/create-llama/templates/ui/shadcn/input.tsx b/packages/create-llama/templates/ui/shadcn/input.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..edfa129e623cca64fa706d7750e3635fecd1d628
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+
+import { cn } from "./lib/utils";
+
+export interface InputProps
+  extends React.InputHTMLAttributes<HTMLInputElement> {}
+
+const Input = React.forwardRef<HTMLInputElement, InputProps>(
+  ({ className, type, ...props }, ref) => {
+    return (
+      <input
+        type={type}
+        className={cn(
+          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+          className,
+        )}
+        ref={ref}
+        {...props}
+      />
+    );
+  },
+);
+Input.displayName = "Input";
+
+export { Input };
diff --git a/packages/create-llama/templates/ui/shadcn/lib/utils.ts b/packages/create-llama/templates/ui/shadcn/lib/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a5ef193506d07d0459fec4f187af08283094d7c8
--- /dev/null
+++ b/packages/create-llama/templates/ui/shadcn/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs));
+}