diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
index ba5ce12f5ce46d190f094e1b50afe2788eba31ae..4a42e40534835cc6886d924af998906e445a3327 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php
@@ -115,6 +115,12 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase
         'preact' => \sprintf('%s%s/ui/lib/astro-hydration/dist/preact.module.js', $base_path, $xb_path),
         'preact/hooks' => \sprintf('%s%s/ui/lib/astro-hydration/dist/hooks.module.js', $base_path, $xb_path),
         'react/jsx-runtime' => \sprintf('%s%s/ui/lib/astro-hydration/dist/jsxRuntime.module.js', $base_path, $xb_path),
+        // @todo Remove this hard-coding and calculate it on a per component
+        // basis - see https://drupal.org/i/3500761.
+        'clsx' => \sprintf('%s%s/ui/lib/astro-hydration/dist/clsx.js', $base_path, $xb_path),
+        'class-variance-authority' => \sprintf('%s%s/ui/lib/astro-hydration/dist/class-variance-authority.js', $base_path, $xb_path),
+        'tailwind-merge' => \sprintf('%s%s/ui/lib/astro-hydration/dist/tailwind-merge.js', $base_path, $xb_path),
+        '@/lib/utils' => \sprintf('%s%s/ui/lib/astro-hydration/dist/utils.js', $base_path, $xb_path),
       ],
       // @todo Rename plugin ID in https://www.drupal.org/project/experience_builder/issues/3502982
       '#component' => $this->configuration['plugin_id'],
diff --git a/tests/src/Kernel/DataType/ComponentTreeHydratedTest.php b/tests/src/Kernel/DataType/ComponentTreeHydratedTest.php
index 273ad589b12ae4fa8f18a5791466ae5cb3b596c9..5f5dce771fe03e50a19d11d92765d787bf3d158e 100644
--- a/tests/src/Kernel/DataType/ComponentTreeHydratedTest.php
+++ b/tests/src/Kernel/DataType/ComponentTreeHydratedTest.php
@@ -804,6 +804,10 @@ HTML,
                                 'preact' => \sprintf('%s/ui/lib/astro-hydration/dist/preact.module.js', $path),
                                 'preact/hooks' => \sprintf('%s/ui/lib/astro-hydration/dist/hooks.module.js', $path),
                                 'react/jsx-runtime' => \sprintf('%s/ui/lib/astro-hydration/dist/jsxRuntime.module.js', $path),
+                                'clsx' => \sprintf('%s/ui/lib/astro-hydration/dist/clsx.js', $path),
+                                'class-variance-authority' => \sprintf('%s/ui/lib/astro-hydration/dist/class-variance-authority.js', $path),
+                                'tailwind-merge' => \sprintf('%s/ui/lib/astro-hydration/dist/tailwind-merge.js', $path),
+                                '@/lib/utils' => \sprintf('%s/ui/lib/astro-hydration/dist/utils.js', $path),
                               ],
                               '#component' => 'my-cta',
                               '#props' => [
diff --git a/ui/lib/astro-hydration/astro.config.mjs b/ui/lib/astro-hydration/astro.config.mjs
index 344ae4cbc1789807cc56f6f85f7c26c71feb5c3b..fee0f554fee34b793167222ae6d3add43cce2c75 100644
--- a/ui/lib/astro-hydration/astro.config.mjs
+++ b/ui/lib/astro-hydration/astro.config.mjs
@@ -1,17 +1,42 @@
 import { defineConfig } from 'astro/config';
 import preact from '@astrojs/preact';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
 
 // https://astro.build/config
 export default defineConfig({
   // Enable Preact to support Preact JSX components.
   integrations: [preact()],
   vite: {
+    resolve: {
+      alias: {
+        '@': path.resolve(__dirname, 'src/'),
+      },
+    },
     build: {
       rollupOptions: {
         output: {
           // Filename pattern for the output files
           entryFileNames: '[name].js',
-          chunkFileNames: '[name].js',
+          chunkFileNames: (chunkInfo) => {
+            // Make sure the output chunks for dependencies have useful file
+            // names so we can easily distinguish between them.
+            const matches = {
+              clsx: 'clsx.js',
+              'class-variance-authority': 'class-variance-authority.js',
+              'tailwind-merge': 'tailwind-merge.js',
+              'lib/astro-hydration/src/lib/utils.ts': 'util.js',
+            };
+            return Object.entries(matches).reduce((carry, [key, value]) => {
+              if (chunkInfo.facadeModuleId?.includes(`node_modules/${key}`)) {
+                return value;
+              }
+              return carry;
+            }, '[name].js');
+          },
           assetFileNames: '[name][extname]',
         },
       },
diff --git a/ui/lib/astro-hydration/package.json b/ui/lib/astro-hydration/package.json
index 429a2cb5907e3526e8d19c65957ec417b087c6b8..24818c95f029fecb02ce72806ef85e806cfa3174 100644
--- a/ui/lib/astro-hydration/package.json
+++ b/ui/lib/astro-hydration/package.json
@@ -8,6 +8,9 @@
   "dependencies": {
     "@astrojs/preact": "^4.0.3",
     "astro": "^5.2.2",
-    "preact": "^10.25.4"
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "preact": "^10.25.4",
+    "tailwind-merge": "^3.0.2"
   }
 }
diff --git a/ui/lib/astro-hydration/src/components/Stub.jsx b/ui/lib/astro-hydration/src/components/Stub.jsx
index a49cc7d38553610399d9f095fdf6157cbb3ef105..c9936f417376b5ddbdb1c1ffd4a99a11aa583110 100644
--- a/ui/lib/astro-hydration/src/components/Stub.jsx
+++ b/ui/lib/astro-hydration/src/components/Stub.jsx
@@ -8,5 +8,9 @@
 
 const { ...preactHooks } = await import('preact/hooks');
 const { jsx, jsxs, Fragment } = await import('preact/jsx-runtime');
+const { default: clsx } = await import('clsx');
+const { ...tailwindMerge } = await import('tailwind-merge');
+const { cva } = await import('class-variance-authority');
+const { cn } = await import('@/lib/utils');
 
 export default function () {}
diff --git a/ui/lib/astro-hydration/src/lib/utils.ts b/ui/lib/astro-hydration/src/lib/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b1ca34a146a3fae4ed54a0099f2ee64aee690255
--- /dev/null
+++ b/ui/lib/astro-hydration/src/lib/utils.ts
@@ -0,0 +1,7 @@
+import { clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+import type { ClassValue } from 'clsx';
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs));
+}
diff --git a/ui/lib/astro-hydration/tsconfig.json b/ui/lib/astro-hydration/tsconfig.json
index eb8b4ca6c22d79d30f606c9aab4da51330024f24..e8e64a216ca716f9b4e3c4f3bcc4a803bbf388ca 100644
--- a/ui/lib/astro-hydration/tsconfig.json
+++ b/ui/lib/astro-hydration/tsconfig.json
@@ -10,6 +10,9 @@
   ],
   "compilerOptions": {
     "jsx": "react-jsx",
-    "jsxImportSource": "preact"
+    "jsxImportSource": "preact",
+    "paths": {
+      "@/*": ["src/*"],
+    }
   }
 }
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 8542a424046c37b3bee7f374d96fa7c056c730bf..77cc4afe3b7856ed1b73b40952336cc0b474e510 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -103,7 +103,10 @@
       "dependencies": {
         "@astrojs/preact": "^4.0.3",
         "astro": "^5.2.2",
-        "preact": "^10.25.4"
+        "class-variance-authority": "^0.7.1",
+        "clsx": "^2.1.1",
+        "preact": "^10.25.4",
+        "tailwind-merge": "^3.0.2"
       }
     },
     "node_modules/@adobe/css-tools": {
@@ -8891,6 +8894,17 @@
         "node": ">=8"
       }
     },
+    "node_modules/class-variance-authority": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+      "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+      "dependencies": {
+        "clsx": "^2.1.1"
+      },
+      "funding": {
+        "url": "https://polar.sh/cva"
+      }
+    },
     "node_modules/classnames": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -17308,6 +17322,15 @@
         "url": "https://opencollective.com/unts"
       }
     },
+    "node_modules/tailwind-merge": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
+      "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/dcastil"
+      }
+    },
     "node_modules/tailwindcss-in-browser": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/tailwindcss-in-browser/-/tailwindcss-in-browser-0.1.3.tgz",
diff --git a/ui/src/features/code-editor/Preview.tsx b/ui/src/features/code-editor/Preview.tsx
index 29293052cfafaf24009a42e166fad8eba2391f6c..ca347a373acf919ae310d5856d97573887307bc0 100644
--- a/ui/src/features/code-editor/Preview.tsx
+++ b/ui/src/features/code-editor/Preview.tsx
@@ -67,6 +67,11 @@ const importMap = {
     'react/': 'https://esm.sh/preact/compat/',
     'react-dom': 'https://esm.sh/preact/compat',
     'react-dom/': 'https://esm.sh/preact/compat/',
+    // @todo Remove hardcoding and allow components to nominate their own?
+    clsx: 'https://esm.sh/clsx',
+    'class-variance-authority': 'https://esm.sh/class-variance-authority',
+    'tailwind-merge': 'https://esm.sh/tailwind-merge',
+    '@/lib/utils': `${XB_MODULE_UI_PATH}/lib/astro-hydration/dist/utils.js`,
   },
 };