From 45e4a473a937d9c90dbd256254a71bc3b25761a0 Mon Sep 17 00:00:00 2001 From: lukeocodes Date: Wed, 6 May 2026 13:40:58 +0100 Subject: [PATCH] fix(ui): scope tailwind preflight to [data-dg-agent] via plugin The full `@import "tailwindcss";` shortcut shipped global preflight rules with universal selectors (`*, ::before, ::after`, `html`, `body`) that bled into every host page that loaded `@deepgram/ui` or `@deepgram/agents-widget`. Symptoms reported in deepgram/deepgram-docs included content width collapse on every non-demo page after the widget bundle finished loading. Fix: split the Tailwind import into theme + utilities (granular, no preflight) and use `tailwindcss-scoped-preflight` to re-introduce preflight scoped to `[data-dg-agent]`. The plugin's `inside` strategy rewrites every preflight rule with `:where([data-dg-agent], [data-dg-agent] *)` so the reset only applies to widget descendants. Verified end-to-end through @deepgram/agents-widget UMD build: before: 0 `:where([data-dg-agent]` matches, full unscoped preflight after: 76 `:where([data-dg-agent]` matches, 0 unscoped preflight Bundle size grew ~3KB after gzip from the added scope selectors, which is acceptable to stop the host-page regression. Also moved `tailwindcss-scoped-preflight` from devDependencies to dependencies so consumers get the plugin transitively when they install @deepgram/ui. The plugin only runs at the consumer's Tailwind build time, so it must be resolvable from the consumer's node_modules. Bonus cleanup: extended tokens that were previously on `:root` are now scoped to `[data-dg-agent]` so they do not leak to the host page either. `--primary-hover`, `--msg-user-bg`, `--dg-va-primary`, and `--dg-va-border` are widget-internal and should never have been on `:root`. --- bun.lock | 5 ++++- packages/ui/package.json | 3 ++- packages/ui/src/styles.css | 28 +++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/bun.lock b/bun.lock index 580c57d..6cd2eb6 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ }, "packages/ui": { "name": "@deepgram/ui", - "version": "0.0.0", + "version": "0.1.0", "dependencies": { "@deepgram/agents": "^0.1.1", "@deepgram/react": "^0.1.0", @@ -36,6 +36,7 @@ "clsx": "2.1.1", "lucide-react": "1.11.0", "tailwind-merge": "3.5.0", + "tailwindcss-scoped-preflight": "^4.0.6", }, "devDependencies": { "@tailwindcss/typography": "0.5.19", @@ -1031,6 +1032,8 @@ "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + "tailwindcss-scoped-preflight": ["tailwindcss-scoped-preflight@4.0.6", "", { "peerDependencies": { "postcss": "^8", "tailwindcss": "^4" } }, "sha512-pG2HSG+S6DAF8+W07KOC/Qf+jnE9YgWytdUWifIpZ7g6uUUpOenhAuck9DwxKLTY2do/3htTHqBgHDVIe8mMqQ=="], + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], "terser": ["terser@5.46.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw=="], diff --git a/packages/ui/package.json b/packages/ui/package.json index 954886e..a3dca78 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -43,7 +43,8 @@ "class-variance-authority": "0.7.1", "clsx": "2.1.1", "lucide-react": "1.11.0", - "tailwind-merge": "3.5.0" + "tailwind-merge": "3.5.0", + "tailwindcss-scoped-preflight": "^4.0.6" }, "peerDependencies": { "react": ">=18.0.0", diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css index d8d5f0b..774802a 100644 --- a/packages/ui/src/styles.css +++ b/packages/ui/src/styles.css @@ -1,5 +1,26 @@ -@import "tailwindcss"; +/* Tailwind v4 imports, granular so we can scope preflight. + * + * The full `@import "tailwindcss";` shortcut pulls preflight in at the global + * scope, which uses universal selectors (`*, ::before, ::after`, `html`, `body`) + * that bleed onto any host page that loads the bundle. The + * `tailwindcss-scoped-preflight` plugin rewrites every preflight rule with a + * `[data-dg-agent]` ancestor, so the reset only applies to widget descendants. + * + * Theme tokens and utilities stay global so embedders building their own + * widget layouts with @deepgram/ui components continue to get class-based + * styling via Tailwind utilities. + * + * Do NOT add `@import "tailwindcss";` back. Granular imports + the plugin are + * the contract that keeps the host page untouched. + */ +@layer theme, base, components, utilities; +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/utilities.css" layer(utilities); @plugin "@tailwindcss/typography"; +@plugin "tailwindcss-scoped-preflight" { + isolation-strategy: inside; + selector: [data-dg-agent]; +} /* Explicitly tell Tailwind v4 to scan component source files for class usage. Without this, auto-detection may miss files in sibling repos / outside the project root. */ @@ -97,8 +118,9 @@ color-scheme: light; } - /* Extended tokens without direct shadcn equivalents */ - :root { + /* Extended tokens without direct shadcn equivalents. + * Scoped to [data-dg-agent] (not :root) so they do not leak to the host. */ + [data-dg-agent] { --primary-hover: color-mix(in srgb, var(--color-primary) 85%, #000); --primary-active: color-mix(in srgb, var(--color-primary) 70%, #000); --accent-active: #f3f4f6;