Skip to content

fix(ui): scope tailwind preflight to [data-dg-agent] via plugin#27

Merged
lukeocodes merged 1 commit intomainfrom
fix/scope-preflight
May 6, 2026
Merged

fix(ui): scope tailwind preflight to [data-dg-agent] via plugin#27
lukeocodes merged 1 commit intomainfrom
fix/scope-preflight

Conversation

@lukeocodes
Copy link
Copy Markdown
Member

Why

The full @import "tailwindcss"; shortcut ships preflight at the global scope. The preflight uses universal selectors (*, ::before, ::after, html, body) that override the host page's box model, list styling, heading margins, button appearance, and more.

Symptoms reported in deepgram/deepgram-docs#777: content width collapsed on every non-demo page after the widget bundle finished loading. Same regression hits any customer embedding @deepgram/agents-widget on their own site, not just the docs.

What changed

  • Granular Tailwind imports: theme + utilities only. Preflight is no longer pulled in at the global scope.
  • New: tailwindcss-scoped-preflight plugin with isolation-strategy: inside and selector: [data-dg-agent]. The plugin re-introduces preflight, but every rule is rewritten as :where([data-dg-agent], [data-dg-agent] *)…. The reset only applies to widget descendants.
  • Moved tailwindcss-scoped-preflight from devDependencies to dependencies. The plugin runs at the consumer's Tailwind build time, so it must be installed transitively when consumers depend on @deepgram/ui.
  • Scoped :root-level extended tokens (--primary-hover, --msg-user-bg, --dg-va-primary, --dg-va-border, etc.) to [data-dg-agent]. These are widget-internal tokens; they should never have been on :root in the first place.

Verification

End-to-end through @deepgram/agents-widget@0.1.3 UMD build (locally patched to consume this @deepgram/ui):

before after
Unscoped *,:after,:before,::backdrop rules in widget bundle yes 0
Scoped :where([data-dg-agent], [data-dg-agent] *) rules in widget bundle 0 76
Total [data-dg-agent] occurrences in widget bundle 9 171

Bundle size grew ~3 KB gzipped from the added scope selectors. Acceptable trade for stopping the host-page regression.

Follow-up

@deepgram/agents-widget needs to bump its @deepgram/ui dependency once this lands and a new @deepgram/ui version publishes. The widget's UMD build will then ship correctly-scoped preflight automatically.

There's a separate (smaller) widget-side issue where the widget's Vite build appears to also process Tailwind directly, producing its own copy of unscoped output. That trail leads to widget vite config and is out of scope for this PR; will file separately.

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`.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui-web Ready Ready Preview, Comment May 6, 2026 0:41am

Request Review

@lukeocodes lukeocodes merged commit 8742c18 into main May 6, 2026
4 checks passed
lukeocodes added a commit to deepgram/agent that referenced this pull request May 6, 2026
#48)

## Why

`@deepgram/ui@0.1.1` (just published from
[deepgram/ui#27](deepgram/ui#27) +
[deepgram/ui#30](deepgram/ui#30)) ships
preflight scoped to `[data-dg-agent]` via
[`tailwindcss-scoped-preflight`](https://github.com/Roman86/tailwindcss-scoped-preflight).
Until the widget bumps its dependency, the published UMD on
`cdn.deepgram.com/widgets/` still ships unscoped Tailwind preflight that
bleeds into host pages.

Symptoms reported in
[deepgram/deepgram-docs#777](deepgram/deepgram-docs#777):
content width collapsed on every non-demo page on the docs site after
the widget bundle finished loading. Same regression hits any customer
embedding the widget on their own site.

## What changed

- `packages/widget/package.json`: `@deepgram/ui` `^0.1.0` → `^0.1.1`
- `bun.lock` regenerated

## Verification

Rebuilt the widget locally against `@deepgram/ui@0.1.1`:

| | before | after |
|---|---|---|
| Unscoped `@layer base{*,:after,…}` rules in widget UMD | many | **0**
|
| `:where([data-dg-agent], …)` scoped rules | 0 | **76** |
| Total `[data-dg-agent]` occurrences | 9 | **171** |

Bundle size: 384.30 KB → 387.53 KB (+3.23 KB) from the added scope
selectors. Acceptable trade.

## Expected after merge

1. release-please opens `chore(main): release agents-widget 0.1.4`.
2. Merging that PR bumps `packages/widget/package.json` to 0.1.4 and
tags `agents-widget-v0.1.4`.
3. The npm publish workflow pushes `@deepgram/agents-widget@0.1.4` to
npm.
4. The CDN publish job uploads the new UMD bundle to:
- `https://cdn.deepgram.com/widgets/v0.1.4/widget.umd.js` (immutable,
365-day TTL)
- `https://cdn.deepgram.com/widgets/latest/widget.umd.js` (5-min TTL
plus invalidation)
5. Anyone loading either URL gets the scoped preflight.

## Refs

- [deepgram/ui#27](deepgram/ui#27) — the source
fix
- [deepgram/ui#30](deepgram/ui#30) — unpin
release-please to actually publish 0.1.1
- [deepgram/dx-stack#4](deepgram/dx-stack#4) —
recipe + gotchas doc
-
[deepgram/deepgram-docs#777](deepgram/deepgram-docs#777)
— where the bleed was first surfaced
lukeocodes added a commit that referenced this pull request May 7, 2026
The 0.1.2 change switched from granular tailwind imports to the full
`@import "tailwindcss" prefix(dg)` shortcut to apply prefix(dg)
cleanly. That shortcut also imports preflight at root scope. The
scoped-preflight plugin still ran and added its own
`:where([data-dg-agent], ...)` copy of every preflight rule, but it
did NOT strip the upstream raw preflight that the full import had
already injected. The 0.1.2 bundle therefore shipped TWO copies of
preflight: one scoped, one raw. The raw copy bled the universal
selector reset onto every host page, undoing the original 0.1.1
work.

Restoring granular imports with prefix() applied per-import:

  @import "tailwindcss/theme.css" layer(theme) prefix(dg);
  @import "tailwindcss/utilities.css" layer(utilities) prefix(dg);

is the configuration documented for "preflight-free" Tailwind v4
setups. Theme tokens and utility classes still get the dg- and dg:
prefix from prefix(). Preflight is not imported at all, so the only
preflight rules in the bundle are the scoped copies the plugin
emits.

Verified locally:

  before (0.1.2): unscoped @layer base{*,:after,:before{...}},
                  raw html{}, body{}, h1,h2,h3{} rules at root
                  scope alongside the scoped versions
  after  (0.1.3): 0 unscoped preflight indicators,
                  76 scoped :where([data-dg-agent], …) rules,
                  353 prefixed .dg\: utility refs,
                  13/13 tests pass

Bundle size: 188.9 KB -> 185.2 KB (-3.7 KB) from removing the
duplicate raw-preflight rules.

Refs:
- https://tailwindcss.com/docs/preflight#disabling-preflight (granular import pattern)
- #27 (the original scoped-preflight work)
- #32 (the prefix(dg) work that introduced this regression)
- deepgram/dx-stack#4 (gotchas doc gains a "full import re-leaks preflight" section)
lukeocodes added a commit to deepgram/agent that referenced this pull request May 7, 2026
…egression (#52)

## Why


[`@deepgram/ui@0.1.2`](https://www.npmjs.com/package/@deepgram/ui/v/0.1.2)
introduced a regression by switching to the full `@import "tailwindcss"`
shortcut. The shortcut applied `prefix(dg)` correctly to utilities but
also pulled raw preflight in at root scope, alongside the
scoped-preflight plugin's scoped copy. The bundle shipped two preflights
and the unscoped one leaked `*, ::before, ::after { box-sizing:
border-box; ... }`, `html{...}`, `body{...}`, and `h1,h2,h3{...}` onto
every host page that loaded the widget.


[`@deepgram/ui@0.1.3`](https://www.npmjs.com/package/@deepgram/ui/v/0.1.3)
(just published from
[deepgram/ui#34](deepgram/ui#34)) restored
granular imports (`tailwindcss/theme.css` + `tailwindcss/utilities.css`
with `prefix(dg)` on each) so preflight comes ONLY from the
scoped-preflight plugin. Every preflight rule in the bundle now has a
`[data-dg-agent]` ancestor.

## What changed

- `packages/widget/package.json`: `@deepgram/ui` `^0.1.2` -> `^0.1.3`
- `bun.lock` regenerated

## Verification

Rebuilt the widget locally against `@deepgram/ui@0.1.3`:

| | 0.1.2 (broken) | 0.1.3 (this PR) |
|---|---|---|
| Unscoped `@layer base{*,...}` block | **1** | **0** |
| Unscoped `html{}`, `body{}`, `h1,h2,h3{}` etc | **multiple** | **0** |
| Scoped `:where([data-dg-agent], …)` preflight | 76 | **76**
(unchanged) |
| Prefixed `.dg\:*` utility refs | 353 | **353** (unchanged) |
| Unprefixed Tailwind utility leaks | 0 | **0** (unchanged) |
| Bundle size | 393.5 KB | **389.8 KB** (-3.7 KB) |

## Expected after merge

1. release-please opens `chore(main): release agents-widget 0.1.6`.
2. Merging publishes `@deepgram/agents-widget@0.1.6` to npm and uploads:
   - `https://cdn.deepgram.com/widgets/v0.1.6/widget.umd.js` (immutable)
- `https://cdn.deepgram.com/widgets/latest/widget.umd.js`
(cache-invalidated)
3. Anyone embedding the widget on their own site stops having their
host-page CSS reset by the widget bundle.

## Refs

- [deepgram/ui#34](deepgram/ui#34) - the
granular-imports fix
- [deepgram/ui#32](deepgram/ui#32) - the
prefix(dg) work
- [deepgram/ui#27](deepgram/ui#27) - the
original scoped-preflight work
- [#50](#50) - the
broken 0.1.2 bump this supersedes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant